Android 8.1 允许安装未知来源权限/允许来自此来源的应用

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

发布了87 篇原创文章 · 获赞 157 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/u012932409/article/details/103814872