Added: Add support for getting external app info

PackageUtils were previously based on using `Context` object to get app info, which was only possible to get for Termux app and its sharedUserId plugins. Now it has been refactored to used `PackageInfo` and `ApplicationInfo` objects to get the info, which will also allow getting info of external apps. However, when targeting sdk `30`, queries entries or `QUERY_ALL_PACKAGES` permission will be required. Check `PackageUtils.isAppInstalled()` for more info.
This commit is contained in:
agnostic-apollo
2021-10-09 02:07:09 +05:00
parent 0da1984b59
commit 1327cef7b4
2 changed files with 231 additions and 38 deletions

View File

@@ -68,6 +68,8 @@ public class PackageUtils {
return packageContext; return packageContext;
} }
/** /**
* Get the {@link PackageInfo} for the package associated with the {@code context}. * Get the {@link PackageInfo} for the package associated with the {@code context}.
* *
@@ -75,7 +77,7 @@ public class PackageUtils {
* @return Returns the {@link PackageInfo}. This will be {@code null} if an exception is raised. * @return Returns the {@link PackageInfo}. This will be {@code null} if an exception is raised.
*/ */
public static PackageInfo getPackageInfoForPackage(@NonNull final Context context) { public static PackageInfo getPackageInfoForPackage(@NonNull final Context context) {
return getPackageInfoForPackage(context, 0); return getPackageInfoForPackage(context, context.getPackageName());
} }
/** /**
@@ -87,13 +89,76 @@ public class PackageUtils {
*/ */
@Nullable @Nullable
public static PackageInfo getPackageInfoForPackage(@NonNull final Context context, final int flags) { public static PackageInfo getPackageInfoForPackage(@NonNull final Context context, final int flags) {
return getPackageInfoForPackage(context, context.getPackageName(), flags);
}
/**
* Get the {@link PackageInfo} for the package associated with the {@code packageName}.
*
* @param context The {@link Context} for operations.
* @param packageName The package name of the package.
* @return Returns the {@link PackageInfo}. This will be {@code null} if an exception is raised.
*/
public static PackageInfo getPackageInfoForPackage(@NonNull final Context context, @NonNull final String packageName) {
return getPackageInfoForPackage(context, packageName, 0);
}
/**
* Get the {@link PackageInfo} for the package associated with the {@code packageName}.
*
* Also check {@link #isAppInstalled(Context, String, String) if targetting targeting sdk
* `30` (android `11`) since {@link PackageManager.NameNotFoundException} may be thrown.
*
* @param context The {@link Context} for operations.
* @param packageName The package name of the package.
* @param flags The flags to pass to {@link PackageManager#getPackageInfo(String, int)}.
* @return Returns the {@link PackageInfo}. This will be {@code null} if an exception is raised.
*/
@Nullable
public static PackageInfo getPackageInfoForPackage(@NonNull final Context context, @NonNull final String packageName, final int flags) {
try { try {
return context.getPackageManager().getPackageInfo(context.getPackageName(), flags); return context.getPackageManager().getPackageInfo(packageName, flags);
} catch (final Exception e) { } catch (final Exception e) {
return null; return null;
} }
} }
/**
* Get the {@link ApplicationInfo} for the {@code packageName}.
*
* @param context The {@link Context} for operations.
* @param packageName The package name of the package.
* @return Returns the {@link ApplicationInfo}. This will be {@code null} if an exception is raised.
*/
@Nullable
public static ApplicationInfo getApplicationInfoForPackage(@NonNull final Context context, @NonNull final String packageName) {
return getApplicationInfoForPackage(context, packageName, 0);
}
/**
* Get the {@link ApplicationInfo} for the {@code packageName}.
*
* Also check {@link #isAppInstalled(Context, String, String) if targetting targeting sdk
* `30` (android `11`) since {@link PackageManager.NameNotFoundException} may be thrown.
*
* @param context The {@link Context} for operations.
* @param packageName The package name of the package.
* @param flags The flags to pass to {@link PackageManager#getApplicationInfo(String, int)}.
* @return Returns the {@link ApplicationInfo}. This will be {@code null} if an exception is raised.
*/
@Nullable
public static ApplicationInfo getApplicationInfoForPackage(@NonNull final Context context, @NonNull final String packageName, final int flags) {
try {
return context.getPackageManager().getApplicationInfo(packageName, flags);
} catch (final Exception e) {
return null;
}
}
/** /**
* Get the app name for the package associated with the {@code context}. * Get the app name for the package associated with the {@code context}.
* *
@@ -101,9 +166,22 @@ public class PackageUtils {
* @return Returns the {@code android:name} attribute. * @return Returns the {@code android:name} attribute.
*/ */
public static String getAppNameForPackage(@NonNull final Context context) { public static String getAppNameForPackage(@NonNull final Context context) {
return context.getApplicationInfo().loadLabel(context.getPackageManager()).toString(); return getAppNameForPackage(context, context.getApplicationInfo());
} }
/**
* Get the app name for the package associated with the {@code applicationInfo}.
*
* @param context The {@link Context} for operations.
* @param applicationInfo The {@link ApplicationInfo} for the package.
* @return Returns the {@code android:name} attribute.
*/
public static String getAppNameForPackage(@NonNull final Context context, @NonNull final ApplicationInfo applicationInfo) {
return applicationInfo.loadLabel(context.getPackageManager()).toString();
}
/** /**
* Get the package name for the package associated with the {@code context}. * Get the package name for the package associated with the {@code context}.
* *
@@ -111,9 +189,21 @@ public class PackageUtils {
* @return Returns the package name. * @return Returns the package name.
*/ */
public static String getPackageNameForPackage(@NonNull final Context context) { public static String getPackageNameForPackage(@NonNull final Context context) {
return context.getApplicationInfo().packageName; return getPackageNameForPackage(context.getApplicationInfo());
} }
/**
* Get the package name for the package associated with the {@code applicationInfo}.
*
* @param applicationInfo The {@link ApplicationInfo} for the package.
* @return Returns the package name.
*/
public static String getPackageNameForPackage(@NonNull final ApplicationInfo applicationInfo) {
return applicationInfo.packageName;
}
/** /**
* Get the {@code targetSdkVersion} for the package associated with the {@code context}. * Get the {@code targetSdkVersion} for the package associated with the {@code context}.
* *
@@ -121,9 +211,21 @@ public class PackageUtils {
* @return Returns the {@code targetSdkVersion}. * @return Returns the {@code targetSdkVersion}.
*/ */
public static int getTargetSDKForPackage(@NonNull final Context context) { public static int getTargetSDKForPackage(@NonNull final Context context) {
return context.getApplicationInfo().targetSdkVersion; return getTargetSDKForPackage(context.getApplicationInfo());
} }
/**
* Get the {@code targetSdkVersion} for the package associated with the {@code applicationInfo}.
*
* @param applicationInfo The {@link ApplicationInfo} for the package.
* @return Returns the {@code targetSdkVersion}.
*/
public static int getTargetSDKForPackage(@NonNull final ApplicationInfo applicationInfo) {
return applicationInfo.targetSdkVersion;
}
/** /**
* Check if the app associated with the {@code context} has {@link ApplicationInfo#FLAG_DEBUGGABLE} * Check if the app associated with the {@code context} has {@link ApplicationInfo#FLAG_DEBUGGABLE}
* set. * set.
@@ -132,9 +234,22 @@ public class PackageUtils {
* @return Returns the {@code versionName}. This will be {@code null} if an exception is raised. * @return Returns the {@code versionName}. This will be {@code null} if an exception is raised.
*/ */
public static Boolean isAppForPackageADebuggableBuild(@NonNull final Context context) { public static Boolean isAppForPackageADebuggableBuild(@NonNull final Context context) {
return ( 0 != ( context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE ) ); return isAppForPackageADebuggableBuild(context.getApplicationInfo());
} }
/**
* Check if the app associated with the {@code applicationInfo} has {@link ApplicationInfo#FLAG_DEBUGGABLE}
* set.
*
* @param applicationInfo The {@link ApplicationInfo} for the package.
* @return Returns the {@code versionName}. This will be {@code null} if an exception is raised.
*/
public static Boolean isAppForPackageADebuggableBuild(@NonNull final ApplicationInfo applicationInfo) {
return ( 0 != ( applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE ) );
}
/** /**
* Check if the app associated with the {@code context} has {@link ApplicationInfo#FLAG_EXTERNAL_STORAGE} * Check if the app associated with the {@code context} has {@link ApplicationInfo#FLAG_EXTERNAL_STORAGE}
* set. * set.
@@ -143,9 +258,22 @@ public class PackageUtils {
* @return Returns the {@code versionName}. This will be {@code null} if an exception is raised. * @return Returns the {@code versionName}. This will be {@code null} if an exception is raised.
*/ */
public static Boolean isAppInstalledOnExternalStorage(@NonNull final Context context) { public static Boolean isAppInstalledOnExternalStorage(@NonNull final Context context) {
return ( 0 != ( context.getApplicationInfo().flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE ) ); return isAppInstalledOnExternalStorage(context.getApplicationInfo());
} }
/**
* Check if the app associated with the {@code applicationInfo} has {@link ApplicationInfo#FLAG_EXTERNAL_STORAGE}
* set.
*
* @param applicationInfo The {@link ApplicationInfo} for the package.
* @return Returns the {@code versionName}. This will be {@code null} if an exception is raised.
*/
public static Boolean isAppInstalledOnExternalStorage(@NonNull final ApplicationInfo applicationInfo) {
return ( 0 != ( applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE ) );
}
/** /**
* Get the {@code versionCode} for the package associated with the {@code context}. * Get the {@code versionCode} for the package associated with the {@code context}.
* *
@@ -154,13 +282,27 @@ public class PackageUtils {
*/ */
@Nullable @Nullable
public static Integer getVersionCodeForPackage(@NonNull final Context context) { public static Integer getVersionCodeForPackage(@NonNull final Context context) {
return getVersionCodeForPackage(context, context.getPackageName());
}
/**
* Get the {@code versionCode} for the {@code packageName}.
*
* @param context The {@link Context} for operations.
* @param packageName The package name of the package.
* @return Returns the {@code versionCode}. This will be {@code null} if an exception is raised.
*/
@Nullable
public static Integer getVersionCodeForPackage(@NonNull final Context context, @NonNull final String packageName) {
try { try {
return getPackageInfoForPackage(context).versionCode; return getPackageInfoForPackage(context, packageName).versionCode;
} catch (final Exception e) { } catch (final Exception e) {
return null; return null;
} }
} }
/** /**
* Get the {@code versionName} for the package associated with the {@code context}. * Get the {@code versionName} for the package associated with the {@code context}.
* *
@@ -169,13 +311,26 @@ public class PackageUtils {
*/ */
@Nullable @Nullable
public static String getVersionNameForPackage(@NonNull final Context context) { public static String getVersionNameForPackage(@NonNull final Context context) {
return getVersionNameForPackage(context, context.getPackageName());
}
/**
* Get the {@code versionName} for the {@code packageName}.
*
* @param context The {@link Context} for operations.
* @param packageName The package name of the package.
* @return Returns the {@code versionName}. This will be {@code null} if an exception is raised.
*/
@Nullable
public static String getVersionNameForPackage(@NonNull final Context context, @NonNull final String packageName) {
try { try {
return getPackageInfoForPackage(context).versionName; return getPackageInfoForPackage(context, packageName).versionName;
} catch (final Exception e) { } catch (final Exception e) {
return null; return null;
} }
} }
/** /**
* Get the {@code SHA-256 digest} of signing certificate for the package associated with the {@code context}. * Get the {@code SHA-256 digest} of signing certificate for the package associated with the {@code context}.
* *
@@ -184,16 +339,28 @@ public class PackageUtils {
*/ */
@Nullable @Nullable
public static String getSigningCertificateSHA256DigestForPackage(@NonNull final Context context) { public static String getSigningCertificateSHA256DigestForPackage(@NonNull final Context context) {
return getSigningCertificateSHA256DigestForPackage(context, context.getPackageName());
}
/**
* Get the {@code SHA-256 digest} of signing certificate for the {@code packageName}.
*
* @param context The {@link Context} for operations.
* @param packageName The package name of the package.
* @return Returns the {@code SHA-256 digest}. This will be {@code null} if an exception is raised.
*/
@Nullable
public static String getSigningCertificateSHA256DigestForPackage(@NonNull final Context context, @NonNull final String packageName) {
try { try {
/* /*
* Todo: We may need AndroidManifest queries entries if package is installed but with a different signature on android 11 * Todo: We may need AndroidManifest queries entries if package is installed but with a different signature on android 11
* https://developer.android.com/training/package-visibility * https://developer.android.com/training/package-visibility
* Need a device that allows (manual) installation of apk with mismatched signature of * Need a device that allows (manual) installation of apk with mismatched signature of
* sharedUserId apps to test. Currently, if its done, PackageManager just doesn't load * sharedUserId apps to test. Currently, if its done, PackageManager just doesn't load
* the package and removes its apk automatically if its installed as a user app instead of system app * the package and removes its apk automatically if its installed as a user app instead of system app
* W/PackageManager: Failed to parse /path/to/com.termux.tasker.apk: Signature mismatch for shared user: SharedUserSetting{xxxxxxx com.termux/10xxx} * W/PackageManager: Failed to parse /path/to/com.termux.tasker.apk: Signature mismatch for shared user: SharedUserSetting{xxxxxxx com.termux/10xxx}
*/ */
PackageInfo packageInfo = getPackageInfoForPackage(context, PackageManager.GET_SIGNATURES); PackageInfo packageInfo = getPackageInfoForPackage(context, packageName, PackageManager.GET_SIGNATURES);
if (packageInfo == null) return null; if (packageInfo == null) return null;
return DataUtils.bytesToHex(MessageDigest.getInstance("SHA-256").digest(packageInfo.signatures[0].toByteArray())); return DataUtils.bytesToHex(MessageDigest.getInstance("SHA-256").digest(packageInfo.signatures[0].toByteArray()));
} catch (final Exception e) { } catch (final Exception e) {
@@ -287,7 +454,8 @@ public class PackageUtils {
* If your third-party app is targeting sdk `30` (android `11`), then it needs to add package * If your third-party app is targeting sdk `30` (android `11`), then it needs to add package
* name to the `queries` element or request `QUERY_ALL_PACKAGES` permission in its * name to the `queries` element or request `QUERY_ALL_PACKAGES` permission in its
* `AndroidManifest.xml`. Otherwise it will get `PackageSetting{...... package_name/......} BLOCKED` * `AndroidManifest.xml`. Otherwise it will get `PackageSetting{...... package_name/......} BLOCKED`
* errors in `logcat` and `RUN_COMMAND` won't work. * errors in `logcat` and {@link PackageManager.NameNotFoundException} may be thrown.
* `RUN_COMMAND` intent won't work either.
* Check [package-visibility](https://developer.android.com/training/basics/intents/package-visibility#package-name), * Check [package-visibility](https://developer.android.com/training/basics/intents/package-visibility#package-name),
* `QUERY_ALL_PACKAGES` [googleplay policy](https://support.google.com/googleplay/android-developer/answer/10158779 * `QUERY_ALL_PACKAGES` [googleplay policy](https://support.google.com/googleplay/android-developer/answer/10158779
* and this [article](https://medium.com/androiddevelopers/working-with-package-visibility-dc252829de2d) for more info. * and this [article](https://medium.com/androiddevelopers/working-with-package-visibility-dc252829de2d) for more info.
@@ -295,25 +463,24 @@ public class PackageUtils {
* {@code * {@code
* <manifest * <manifest
* <queries> * <queries>
* <package android:name="package_name" /> * <package android:name="com.termux" />
* </queries> * </queries>
*
* <application
* ....
* </application>
* </manifest> * </manifest>
* } * }
* *
* @param context The context for operations. * @param context The context for operations.
* @param appName The name of the app.
* @param packageName The package name of the package.
* @return Returns {@code errmsg} if {@code packageName} is not installed or disabled, otherwise {@code null}. * @return Returns {@code errmsg} if {@code packageName} is not installed or disabled, otherwise {@code null}.
*/ */
public static String isAppInstalled(@NonNull final Context context, String appName, String packageName) { public static String isAppInstalled(@NonNull final Context context, String appName, String packageName) {
String errmsg = null; String errmsg = null;
PackageManager packageManager = context.getPackageManager(); ApplicationInfo applicationInfo = getApplicationInfoForPackage(context, packageName);
ApplicationInfo applicationInfo;
try {
applicationInfo = packageManager.getApplicationInfo(packageName, 0);
} catch (final PackageManager.NameNotFoundException e) {
applicationInfo = null;
}
boolean isAppEnabled = (applicationInfo != null && applicationInfo.enabled); boolean isAppEnabled = (applicationInfo != null && applicationInfo.enabled);
// If app is not installed or is disabled // If app is not installed or is disabled

View File

@@ -2,6 +2,7 @@ package com.termux.shared.termux;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -26,6 +27,9 @@ public class AndroidUtils {
/** /**
* Get a markdown {@link String} for the app info for the package associated with the {@code context}. * Get a markdown {@link String} for the app info for the package associated with the {@code context}.
* This will contain additional info about the app in addition to the one returned by
* {@link #getAppInfoMarkdownString(Context, String)}, which will be got via the {@code context}
* object.
* *
* @param context The context for operations for the package. * @param context The context for operations for the package.
* @return Returns the markdown {@link String}. * @return Returns the markdown {@link String}.
@@ -33,21 +37,16 @@ public class AndroidUtils {
public static String getAppInfoMarkdownString(@NonNull final Context context) { public static String getAppInfoMarkdownString(@NonNull final Context context) {
StringBuilder markdownString = new StringBuilder(); StringBuilder markdownString = new StringBuilder();
AndroidUtils.appendPropertyToMarkdown(markdownString,"APP_NAME", PackageUtils.getAppNameForPackage(context)); String appInfo = getAppInfoMarkdownString(context, context.getPackageName());
AndroidUtils.appendPropertyToMarkdown(markdownString,"PACKAGE_NAME", PackageUtils.getPackageNameForPackage(context)); if (appInfo == null)
AndroidUtils.appendPropertyToMarkdown(markdownString,"VERSION_NAME", PackageUtils.getVersionNameForPackage(context)); return markdownString.toString();
AndroidUtils.appendPropertyToMarkdown(markdownString,"VERSION_CODE", PackageUtils.getVersionCodeForPackage(context)); else
AndroidUtils.appendPropertyToMarkdown(markdownString,"TARGET_SDK", PackageUtils.getTargetSDKForPackage(context)); markdownString.append(appInfo);
AndroidUtils.appendPropertyToMarkdown(markdownString,"IS_DEBUGGABLE_BUILD", PackageUtils.isAppForPackageADebuggableBuild(context));
if (PackageUtils.isAppInstalledOnExternalStorage(context)) {
AndroidUtils.appendPropertyToMarkdown(markdownString,"IS_INSTALLED_ON_EXTERNAL_STORAGE", true);
}
String filesDir = context.getFilesDir().getAbsolutePath(); String filesDir = context.getFilesDir().getAbsolutePath();
if (!filesDir.equals("/data/user/0/" + context.getPackageName() + "/files") && if (!filesDir.equals("/data/user/0/" + context.getPackageName() + "/files") &&
!filesDir.equals("/data/data/" + context.getPackageName() + "/files")) !filesDir.equals("/data/data/" + context.getPackageName() + "/files"))
AndroidUtils.appendPropertyToMarkdown(markdownString,"FILES_DIR", filesDir); AndroidUtils.appendPropertyToMarkdown(markdownString,"FILES_DIR", filesDir);
Long userId = PackageUtils.getSerialNumberForCurrentUser(context); Long userId = PackageUtils.getSerialNumberForCurrentUser(context);
if (userId == null || userId != 0) if (userId == null || userId != 0)
@@ -58,6 +57,33 @@ public class AndroidUtils {
return markdownString.toString(); return markdownString.toString();
} }
/**
* Get a markdown {@link String} for the app info for the {@code packageName}.
*
* @param context The {@link Context} for operations.
* @param packageName The package name of the package.
* @return Returns the markdown {@link String}.
*/
public static String getAppInfoMarkdownString(@NonNull final Context context, @NonNull final String packageName) {
ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoForPackage(context, packageName);
if (applicationInfo == null) return null;
StringBuilder markdownString = new StringBuilder();
AndroidUtils.appendPropertyToMarkdown(markdownString,"APP_NAME", PackageUtils.getAppNameForPackage(context, applicationInfo));
AndroidUtils.appendPropertyToMarkdown(markdownString,"PACKAGE_NAME", PackageUtils.getPackageNameForPackage(applicationInfo));
AndroidUtils.appendPropertyToMarkdown(markdownString,"VERSION_NAME", PackageUtils.getVersionNameForPackage(context, packageName));
AndroidUtils.appendPropertyToMarkdown(markdownString,"VERSION_CODE", PackageUtils.getVersionCodeForPackage(context, packageName));
AndroidUtils.appendPropertyToMarkdown(markdownString,"TARGET_SDK", PackageUtils.getTargetSDKForPackage(applicationInfo));
AndroidUtils.appendPropertyToMarkdown(markdownString,"IS_DEBUGGABLE_BUILD", PackageUtils.isAppForPackageADebuggableBuild(applicationInfo));
if (PackageUtils.isAppInstalledOnExternalStorage(applicationInfo)) {
AndroidUtils.appendPropertyToMarkdown(markdownString,"IS_INSTALLED_ON_EXTERNAL_STORAGE", true);
}
return markdownString.toString();
}
/** /**
* Get a markdown {@link String} for the device info. * Get a markdown {@link String} for the device info.
* *