package com.termux.shared.android; import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.UserHandle; import android.os.UserManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.termux.shared.R; import com.termux.shared.data.DataUtils; import com.termux.shared.interact.MessageDialogUtils; import com.termux.shared.logger.Logger; import com.termux.shared.reflection.ReflectionUtils; import java.lang.reflect.Field; import java.security.MessageDigest; import java.util.List; public class PackageUtils { private static final String LOG_TAG = "PackageUtils"; /** * Get the {@link Context} for the package name with {@link Context#CONTEXT_RESTRICTED} flags. * * @param context The {@link Context} to use to get the {@link Context} of the {@code packageName}. * @param packageName The package name whose {@link Context} to get. * @return Returns the {@link Context}. This will {@code null} if an exception is raised. */ @Nullable public static Context getContextForPackage(@NonNull final Context context, String packageName) { return getContextForPackage(context, packageName, Context.CONTEXT_RESTRICTED); } /** * Get the {@link Context} for the package name. * * @param context The {@link Context} to use to get the {@link Context} of the {@code packageName}. * @param packageName The package name whose {@link Context} to get. * @param flags The flags for {@link Context} type. * @return Returns the {@link Context}. This will {@code null} if an exception is raised. */ @Nullable public static Context getContextForPackage(@NonNull final Context context, String packageName, int flags) { try { return context.createPackageContext(packageName, flags); } catch (Exception e) { Logger.logVerbose(LOG_TAG, "Failed to get \"" + packageName + "\" package context with flags " + flags + ": " + e.getMessage()); return null; } } /** * Get the {@link Context} for a package name. * * @param context The {@link Context} to use to get the {@link Context} of the {@code packageName}. * @param packageName The package name whose {@link Context} to get. * @param exitAppOnError If {@code true} and failed to get package context, then a dialog will * be shown which when dismissed will exit the app. * @param helpUrl The help user to add to {@link R.string#error_get_package_context_failed_help_url_message}. * @return Returns the {@link Context}. This will {@code null} if an exception is raised. */ @Nullable public static Context getContextForPackageOrExitApp(@NonNull Context context, String packageName, final boolean exitAppOnError, @Nullable String helpUrl) { Context packageContext = getContextForPackage(context, packageName); if (packageContext == null && exitAppOnError) { String errorMessage = context.getString(R.string.error_get_package_context_failed_message, packageName); if (!DataUtils.isNullOrEmpty(helpUrl)) errorMessage += "\n" + context.getString(R.string.error_get_package_context_failed_help_url_message, helpUrl); Logger.logError(LOG_TAG, errorMessage); MessageDialogUtils.exitAppWithErrorMessage(context, context.getString(R.string.error_get_package_context_failed_title), errorMessage); } return packageContext; } /** * Get the {@link PackageInfo} for the package associated with the {@code context}. * * @param context The {@link Context} for 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) { return getPackageInfoForPackage(context, context.getPackageName()); } /** * Get the {@link PackageInfo} for the package associated with the {@code context}. * * @param context The {@link Context} for 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, 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 { return context.getPackageManager().getPackageInfo(packageName, flags); } catch (final Exception e) { 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 {@code privateFlags} {@link Field} of the {@link ApplicationInfo} class. * * @param applicationInfo The {@link ApplicationInfo} for the package. * @return Returns the private flags or {@code null} if an exception was raised. */ @Nullable public static Integer getApplicationInfoPrivateFlagsForPackage(@NonNull final ApplicationInfo applicationInfo) { ReflectionUtils.bypassHiddenAPIReflectionRestrictions(); try { return (Integer) ReflectionUtils.invokeField(ApplicationInfo.class, "privateFlags", applicationInfo).value; } catch (Exception e) { // ClassCastException may be thrown Logger.logStackTraceWithMessage(LOG_TAG, "Failed to get privateFlags field value for ApplicationInfo class", e); return null; } } /** * Get the {@code privateFlags} {@link Field} of the {@link ApplicationInfo} class. * * @param fieldName The name of the field to get. * @return Returns the field value or {@code null} if an exception was raised. */ @Nullable public static Integer getApplicationInfoStaticIntFieldValue(@NonNull String fieldName) { ReflectionUtils.bypassHiddenAPIReflectionRestrictions(); try { return (Integer) ReflectionUtils.invokeField(ApplicationInfo.class, fieldName, null).value; } catch (Exception e) { // ClassCastException may be thrown Logger.logStackTraceWithMessage(LOG_TAG, "Failed to get \"" + fieldName + "\" field value for ApplicationInfo class", e); return null; } } /** * Check if the app associated with the {@code applicationInfo} has a specific flag set. * * @param flagToCheckName The name of the field for the flag to check. * @param applicationInfo The {@link ApplicationInfo} for the package. * @return Returns {@code true} if app has flag is set, otherwise {@code false}. This will be * {@code null} if an exception is raised. */ @Nullable public static Boolean isApplicationInfoPrivateFlagSetForPackage(@NonNull String flagToCheckName, @NonNull final ApplicationInfo applicationInfo) { Integer privateFlags = getApplicationInfoPrivateFlagsForPackage(applicationInfo); if (privateFlags == null) return null; Integer flagToCheck = getApplicationInfoStaticIntFieldValue(flagToCheckName); if (flagToCheck == null) return null; return ( 0 != ( privateFlags & flagToCheck ) ); } /** * Get the app name for the package associated with the {@code context}. * * @param context The {@link Context} for the package. * @return Returns the {@code android:name} attribute. */ public static String getAppNameForPackage(@NonNull final Context context) { 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}. * * @param context The {@link Context} for the package. * @return Returns the package name. */ public static String getPackageNameForPackage(@NonNull final Context context) { 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 uid for the package associated with the {@code context}. * * @param context The {@link Context} for the package. * @return Returns the uid. */ public static int getUidForPackage(@NonNull final Context context) { return getUidForPackage(context.getApplicationInfo()); } /** * Get the uid for the package associated with the {@code applicationInfo}. * * @param applicationInfo The {@link ApplicationInfo} for the package. * @return Returns the uid. */ public static int getUidForPackage(@NonNull final ApplicationInfo applicationInfo) { return applicationInfo.uid; } /** * Get the {@code targetSdkVersion} for the package associated with the {@code context}. * * @param context The {@link Context} for the package. * @return Returns the {@code targetSdkVersion}. */ public static int getTargetSDKForPackage(@NonNull final Context context) { 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} * set. * * @param context The {@link Context} for the package. * @return Returns {@code true} if app is debuggable, otherwise {@code false}. */ public static boolean isAppForPackageADebuggableBuild(@NonNull final Context context) { 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 {@code true} if app is debuggable, otherwise {@code false}. */ 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} * set. * * @param context The {@link Context} for the package. * @return Returns {@code true} if app is installed on external storage, otherwise {@code false}. */ public static boolean isAppInstalledOnExternalStorage(@NonNull final Context context) { 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 {@code true} if app is installed on external storage, otherwise {@code false}. */ public static boolean isAppInstalledOnExternalStorage(@NonNull final ApplicationInfo applicationInfo) { return ( 0 != ( applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE ) ); } /** * Check if the app associated with the {@code context} has * ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE (requestLegacyExternalStorage) * set to {@code true} in app manifest. * * @param context The {@link Context} for the package. * @return Returns {@code true} if app has requested legacy external storage, otherwise * {@code false}. This will be {@code null} if an exception is raised. */ @Nullable public static Boolean hasRequestedLegacyExternalStorage(@NonNull final Context context) { return hasRequestedLegacyExternalStorage(context.getApplicationInfo()); } /** * Check if the app associated with the {@code applicationInfo} has * ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE (requestLegacyExternalStorage) * set to {@code true} in app manifest. * * @param applicationInfo The {@link ApplicationInfo} for the package. * @return Returns {@code true} if app has requested legacy external storage, otherwise * {@code false}. This will be {@code null} if an exception is raised. */ @Nullable public static Boolean hasRequestedLegacyExternalStorage(@NonNull final ApplicationInfo applicationInfo) { return isApplicationInfoPrivateFlagSetForPackage("PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE", applicationInfo); } /** * Get the {@code versionCode} for the package associated with the {@code context}. * * @param context The {@link Context} for 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) { 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) { return getVersionCodeForPackage(getPackageInfoForPackage(context, packageName)); } /** * Get the {@code versionCode} for the {@code packageName}. * * @param packageInfo The {@link PackageInfo} for the package. * @return Returns the {@code versionCode}. This will be {@code null} if an exception is raised. */ @Nullable public static Integer getVersionCodeForPackage(@Nullable final PackageInfo packageInfo) { return packageInfo != null ? packageInfo.versionCode : null; } /** * Get the {@code versionName} for the package associated with the {@code context}. * * @param context The {@link Context} for 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) { 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) { return getVersionNameForPackage(getPackageInfoForPackage(context, packageName)); } /** * Get the {@code versionName} for the {@code packageName}. * * @param packageInfo The {@link PackageInfo} for the package. * @return Returns the {@code versionName}. This will be {@code null} if an {@code packageInfo} * is {@code null}. */ @Nullable public static String getVersionNameForPackage(@Nullable final PackageInfo packageInfo) { return packageInfo != null ? packageInfo.versionName : null; } /** * Get the {@code SHA-256 digest} of signing certificate for the package associated with the {@code context}. * * @param context The {@link Context} for 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) { 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 { /* * 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 * 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 * 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} */ PackageInfo packageInfo = getPackageInfoForPackage(context, packageName, PackageManager.GET_SIGNATURES); if (packageInfo == null) return null; return DataUtils.bytesToHex(MessageDigest.getInstance("SHA-256").digest(packageInfo.signatures[0].toByteArray())); } catch (final Exception e) { return null; } } /** * Get the serial number for the user for the package associated with the {@code context}. * * @param context The {@link Context} for the package. * @return Returns the serial number. This will be {@code null} if failed to get it. */ @RequiresApi(api = Build.VERSION_CODES.N) @Nullable public static Long getUserIdForPackage(@NonNull Context context) { UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); if (userManager == null) return null; return userManager.getSerialNumberForUser(UserHandle.getUserHandleForUid(getUidForPackage(context))); } /** * Check if the current user is the primary user. This is done by checking if the the serial * number for the current user equals 0. * * @param context The {@link Context} for operations. * @return Returns {@code true} if the current user is the primary user, otherwise [@code false}. */ @RequiresApi(api = Build.VERSION_CODES.N) public static boolean isCurrentUserThePrimaryUser(@NonNull Context context) { Long userId = getUserIdForPackage(context); return userId != null && userId == 0; } /** * Get the profile owner package name for the current user. * * @param context The {@link Context} for operations. * @return Returns the profile owner package name. This will be {@code null} if failed to get it * or no profile owner for the current user. */ @Nullable public static String getProfileOwnerPackageNameForUser(@NonNull Context context) { DevicePolicyManager devicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); if (devicePolicyManager == null) return null; List activeAdmins = devicePolicyManager.getActiveAdmins(); if (activeAdmins != null){ for (ComponentName admin:activeAdmins){ String packageName = admin.getPackageName(); if(devicePolicyManager.isProfileOwnerApp(packageName)) return packageName; } } return null; } /** * Get the process id of the main app process of a package. This will work for sharedUserId. Note * that some apps have multiple processes for the app like with `android:process=":background"` * attribute in AndroidManifest.xml. * * @param context The {@link Context} for operations. * @param packageName The package name of the process. * @return Returns the process if found and running, otherwise {@code null}. */ @Nullable public static String getPackagePID(final Context context, String packageName) { ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (activityManager != null) { List processInfos = activityManager.getRunningAppProcesses(); if (processInfos != null) { ActivityManager.RunningAppProcessInfo processInfo; for (int i = 0; i < processInfos.size(); i++) { processInfo = processInfos.get(i); if (processInfo.processName.equals(packageName)) return String.valueOf(processInfo.pid); } } } return null; } /** * Check if app is installed and enabled. This can be used by external apps that don't * share `sharedUserId` with the an app. * * 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 * `AndroidManifest.xml`. Otherwise it will get `PackageSetting{...... package_name/......} BLOCKED` * 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), * `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. * * {@code * * * * * * * } * * @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}. */ public static String isAppInstalled(@NonNull final Context context, String appName, String packageName) { String errmsg = null; ApplicationInfo applicationInfo = getApplicationInfoForPackage(context, packageName); boolean isAppEnabled = (applicationInfo != null && applicationInfo.enabled); // If app is not installed or is disabled if (!isAppEnabled) errmsg = context.getString(R.string.error_app_not_installed_or_disabled_warning, appName, packageName); return errmsg; } /** * Enable or disable a {@link ComponentName} with a call to * {@link PackageManager#setComponentEnabledSetting(ComponentName, int, int)}. * * @param context The {@link Context} for operations. * @param packageName The package name of the component. * @param className The {@link Class} name of the component. * @param state If component should be enabled or disabled. * @param toastString If this is not {@code null} or empty, then a toast before setting state. * @param showErrorMessage If an error message toast should be shown. * @return Returns the errmsg if failed to set state, otherwise {@code null}. */ @Nullable public static String setComponentState(@NonNull final Context context, @NonNull String packageName, @NonNull String className, boolean state, String toastString, boolean showErrorMessage) { try { PackageManager packageManager = context.getPackageManager(); if (packageManager != null) { ComponentName componentName = new ComponentName(packageName, className); if (toastString != null) Logger.showToast(context, toastString, true); packageManager.setComponentEnabledSetting(componentName, state ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); } return null; } catch (final Exception e) { String errmsg = context.getString( state ? R.string.error_enable_component_failed : R.string.error_disable_component_failed, packageName, className) + ": " + e.getMessage(); if (showErrorMessage) Logger.showToast(context, errmsg, true); return errmsg; } } /** * Check if state of a {@link ComponentName} is {@link PackageManager#COMPONENT_ENABLED_STATE_DISABLED} * with a call to {@link PackageManager#getComponentEnabledSetting(ComponentName)}. * * @param context The {@link Context} for operations. * @param packageName The package name of the component. * @param className The {@link Class} name of the component. * @param logErrorMessage If an error message should be logged. * @return Returns {@code true} if disabled, {@code false} if not and {@code null} if failed to * get the state. */ public static Boolean isComponentDisabled(@NonNull final Context context, @NonNull String packageName, @NonNull String className, boolean logErrorMessage) { try { PackageManager packageManager = context.getPackageManager(); if (packageManager != null) { ComponentName componentName = new ComponentName(packageName, className); // Will throw IllegalArgumentException: Unknown component: ComponentInfo{} if app // for context is not installed or component does not exist. return packageManager.getComponentEnabledSetting(componentName) == PackageManager.COMPONENT_ENABLED_STATE_DISABLED; } } catch (final Exception e) { if (logErrorMessage) Logger.logStackTraceWithMessage(LOG_TAG, context.getString(R.string.error_get_component_state_failed, packageName, className), e); } return null; } /** * Check if an {@link android.app.Activity} {@link ComponentName} can be called by calling * {@link PackageManager#queryIntentActivities(Intent, int)}. * * @param context The {@link Context} for operations. * @param packageName The package name of the component. * @param className The {@link Class} name of the component. * @param flags The flags to filter results. * @return Returns {@code true} if it exists, otherwise {@code false}. */ public static boolean doesActivityComponentExist(@NonNull final Context context, @NonNull String packageName, @NonNull String className, int flags) { try { PackageManager packageManager = context.getPackageManager(); if (packageManager != null) { Intent intent = new Intent(); intent.setClassName(packageName, className); return packageManager.queryIntentActivities(intent, flags).size() > 0; } } catch (final Exception e) { // ignore } return false; } }