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