之前 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