之前 6.0 的未知来源权限是一个总的权限,现在单独分开了具体到 app 对应的权限了。具体可见截图
安装未知来源权限其实就是这货 Manifest.permission.REQUEST_INSTALL_PACKAGES,具体的修改代码方案已经在上篇 Android9.0/8.1/6.0 默认给系统 app 授予所有权限中提供了。这篇只是分析解题思路。
核心方法如下
if (checkInstallPackagesPermission(pkgName, mPackageInfo)) { Log.e(TAG, pkgName + " need grant INSTALL_PACKAGES permission"); mAppOpsManager.setMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, mPackageInfo.applicationInfo.uid, pkgName, AppOpsManager.MODE_ALLOWED); Log.e(TAG, "grant INSTALL_PACKAGES permission done"); } private static boolean checkInstallPackagesPermission(String packageName, PackageInfo mPackageInfo){ int uid = mPackageInfo.applicationInfo.uid; //boolean permissionGranted = hasPermission(Manifest.permission.REQUEST_INSTALL_PACKAGES, uid); boolean permissionRequested = hasRequestedAppOpPermission(Manifest.permission.REQUEST_INSTALL_PACKAGES, packageName); int appOpMode = getAppOpMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, uid, packageName); return appOpMode != AppOpsManager.MODE_DEFAULT || permissionRequested; } private static int getAppOpMode(int appOpCode, int uid, String packageName) { return mAppOpsManager.checkOpNoThrow(appOpCode, uid, packageName); } private static boolean hasRequestedAppOpPermission(String permission, String packageName) { try { String[] packages = mIpm.getAppOpPermissionPackages(permission); return ArrayUtils.contains(packages, packageName); } catch (Exception exc) { Log.e(TAG, "PackageManager dead. Cannot get permission info"); return false; } }
从 Settings 说起,我们看见的设置界面中有允许未知来源的 Preference,经过搜索找到 InstalledAppDetails,允许未知来源是动态增加的 Preference ,看如下代码
vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\applications\InstalledAppDetails.java
private void addDynamicPrefs() { if (UserManager.get(getContext()).isManagedProfile()) { return; } ... boolean isPotentialAppSource = isPotentialAppSource(); if (isPotentialAppSource) { Preference pref = new Preference(getPrefContext()); pref.setTitle(R.string.install_other_apps); pref.setKey("install_other_apps"); pref.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { startAppInfoFragment(ExternalSourcesDetails.class, getString(R.string.install_other_apps)); return true; } }); category.addPreference(pref); } } addAppInstallerInfoPref(screen); maybeAddInstantAppButtons(); } private boolean isPotentialAppSource() { AppStateInstallAppsBridge.InstallAppsState appState = new AppStateInstallAppsBridge(getContext(), null, null) .createInstallAppsStateFor(mPackageName, mPackageInfo.applicationInfo.uid); return appState.isPotentialAppSource(); }
isPotentialAppSource 值决定当前 app 详情页面是否需要显示允许来自此来源的应用,isPotentialAppSource() 中初始化了 AppStateInstallAppsBridge对象,并由该对象的isPotentialAppSource()返回。
vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\applications\AppStateInstallAppsBridge.java
InstallAppsState createInstallAppsStateFor(String packageName, int uid) { final InstallAppsState appState = new InstallAppsState(); appState.permissionRequested = hasRequestedAppOpPermission( Manifest.permission.REQUEST_INSTALL_PACKAGES, packageName); appState.permissionGranted = hasPermission(Manifest.permission.REQUEST_INSTALL_PACKAGES, uid); appState.appOpMode = getAppOpMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, uid, packageName); return appState; } public static class InstallAppsState { boolean permissionRequested; boolean permissionGranted; int appOpMode; public InstallAppsState() { this.appOpMode = AppOpsManager.MODE_DEFAULT; } .... public boolean isPotentialAppSource() { Log.e("ExternalSources","appOpMode="+(appOpMode != AppOpsManager.MODE_DEFAULT)); Log.e("ExternalSources","permissionRequested="+permissionRequested); return appOpMode != AppOpsManager.MODE_DEFAULT || permissionRequested; } .... }
InstallAppsState 构造函数初始化将赋值 appOpMode = AppOpsManager.MODE_DEFAULT,
appOpMode 的取值有
public static final int MODE_ALLOWED = 0;
public static final int MODE_IGNORED = 1;
public static final int MODE_ERRORED = 2;
public static final int MODE_DEFAULT = 3;
isPotentialAppSource() 的返回值取决于 appOpMode 和 permissionRequested,这两值在 createInstallAppsStateFor() 被重新赋值,继续看对应的方法
public AppStateInstallAppsBridge(Context context, ApplicationsState appState, Callback callback) { super(appState, callback); mIpm = AppGlobals.getPackageManager(); mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); } private boolean hasRequestedAppOpPermission(String permission, String packageName) { try { Log.e(TAG, "packageName "+packageName); String[] packages = mIpm.getAppOpPermissionPackages(permission); for (String pck : packages) { Log.e(TAG, "PackageManager "+pck); } return ArrayUtils.contains(packages, packageName); } catch (RemoteException exc) { Log.e(TAG, "PackageManager dead. Cannot get permission info"); return false; } } private int getAppOpMode(int appOpCode, int uid, String packageName) { return mAppOpsManager.checkOpNoThrow(appOpCode, uid, packageName); }
通过 AppOpsManager 获取当前 app 的模式
通过 IPackageManager 获取包含 REQUEST_INSTALL_PACKAGES 权限的包名数组,判断当前包名是否在其中
好了,是否需要显示此权限的逻辑搞清楚了,接下来再看如何授权?
InstalledAppDetails 中 Preference 点击事件对应
startAppInfoFragment(ExternalSourcesDetails.class, getString(R.string.install_other_apps));
对应的页面为 ExternalSourcesDetails
vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\applications\ExternalSourcesDetails.java
@Override public boolean onPreferenceChange(Preference preference, Object newValue) { final boolean checked = (Boolean) newValue; if (preference == mSwitchPref) { if (mInstallAppsState != null && checked != mInstallAppsState.canInstallApps()) { if (Settings.ManageAppExternalSourcesActivity.class.getName().equals( getIntent().getComponent().getClassName())) { setResult(checked ? RESULT_OK : RESULT_CANCELED); } setCanInstallApps(checked); refreshUi(); } return true; } return false; } private void setCanInstallApps(boolean newState) { mAppOpsManager.setMode(AppOpsManager.OP_REQUEST_INSTALL_PACKAGES, mPackageInfo.applicationInfo.uid, mPackageName, newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED); }
点击 RestrictedSwitchPreference 时通过 AppOpsManager 修改 mode 为 AppOpsManager.MODE_ALLOWED