从源码角度看Android如何完成动态权限申请

2023-06-18  本文已影响0人  蜗牛是不是牛

伙伴们应该都了解,在Android6.0之后,对于部分权限,例如Camera、读写存储权限等,都需要用户授权才可以使用,除非你的应用为系统应用,否则这些“危险权限”将不会自动授予,那么为什么Google在Android 6.0之后会推出动态权限申请,主要是避免一些“流氓”软件在后台获取用户隐私,从而将责任从技术侧转移到用户侧,既然用户选择了允许这些权限使用,那么责任就由用户承担了。

那么我们在动态申请权限时,系统是如何处理并保存这些状态,接下来我们深入源码一看究竟。

1 权限申请

假如我们的app需要申请读写权限,那么就可以在清单文件中进行权限的声明

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

当然我们仅仅声明并不起作用,我们需要在页面启动时进行权限的动态声明,我们常用的做法就是:

class MainActivity : AppCompatActivity() {
    private val REQUEST_WRITE_STORAGE_PERMISSION = 0
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //首先判断权限是否申请过,或者说是否已经有这个权限了
        if (ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            //如果已经拿到过了,就不需要申请了
            //没拿到过,需要动态申请
            if (ActivityCompat.shouldShowRequestPermissionRationale(
                    this,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
                )
            ) {
                Log.e("TAG", "之前拒绝过权限,现在重新再次申请")
                ActivityCompat.requestPermissions(
                    this,
                    arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                    REQUEST_WRITE_STORAGE_PERMISSION
                )
                return
            }
            Log.e("TAG", "之前没有拒绝过权限,现在第一次申请")
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                REQUEST_WRITE_STORAGE_PERMISSION
            )
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            REQUEST_WRITE_STORAGE_PERMISSION -> {
                Log.e("TAG", "onRequestPermissionsResult 获取到了权限")
                permissions.forEach {
                    Log.e("TAG", "permissions $it")
                }
                grantResults.forEach {
                    Log.e("TAG", "grantResults $it")
                }
            }
        }
    }
}

这是官方的写法,在申请权限之前,首先会判断 是否获取过这个权限,如果没有获取过,那么就会进行动态权限的申请。当然用户可能之前拒绝过权限,所以这里也会进行一次用户是否拒绝过申请权限的操作,可以做对应的一些交互上的处理,以及权限申请的目的让用户打消顾虑。下面就是用户可能存在的居中操作,做了对应日志的打印:

用户第一次申请权限时拒绝了

2023-06-17 13:43:22.111 30040-30040/com.lay.nowinandroid E/TAG: 之前没有拒绝过权限,现在第一次申请
2023-06-17 13:43:31.824 30040-30040/com.lay.nowinandroid E/TAG: onRequestPermissionsResult 获取到了权限
2023-06-17 13:43:31.824 30040-30040/com.lay.nowinandroid E/TAG: permissions android.permission.WRITE_EXTERNAL_STORAGE
2023-06-17 13:43:31.824 30040-30040/com.lay.nowinandroid E/TAG: grantResults -1

用户第二次拒绝了权限

通过日志发现,这时已经走到了shouldShowRequestPermissionRationale代码块中,系统是知道用户之前拒绝过了权限。

2023-06-17 13:44:32.813 30868-30868/com.lay.nowinandroid E/TAG: 之前拒绝过权限,现在重新再次申请
2023-06-17 13:44:49.968 30868-30868/com.lay.nowinandroid E/TAG: onRequestPermissionsResult 获取到了权限
2023-06-17 13:44:49.969 30868-30868/com.lay.nowinandroid E/TAG: permissions android.permission.WRITE_EXTERNAL_STORAGE
2023-06-17 13:44:49.969 30868-30868/com.lay.nowinandroid E/TAG: grantResults -1

用户接受了权限

2023-06-17 13:45:14.135 30868-30868/com.lay.nowinandroid E/TAG: onRequestPermissionsResult 获取到了权限
2023-06-17 13:45:14.135 30868-30868/com.lay.nowinandroid E/TAG: permissions android.permission.WRITE_EXTERNAL_STORAGE
2023-06-17 13:45:14.135 30868-30868/com.lay.nowinandroid E/TAG: grantResults 0

我们看到,当我们申请权限时,其实在onRequestPermissionsResult回调中,无论是同意还是拒绝都会有对应的输出,如果没有同意权限,那么此时grantResults的值就为-1,如果接受了权限,那么grantResults的值就为0,可以看下面对应的介绍。

/**
 * Permission check result: this is returned by {@link #checkPermission}
 * if the permission has been granted to the given package.
 */
public static final int PERMISSION_GRANTED = 0;

/**
 * Permission check result: this is returned by {@link #checkPermission}
 * if the permission has not been granted to the given package.
 */
public static final int PERMISSION_DENIED = -1;

我们看到这里其实结果为一个数组,因为我们在申请权限时,可以以组为单位,与permissions是一一对应的,我们可以知道,到底哪些权限我们拿到了,哪些没有拿到。

2 requestPermissions源码分析

前面在介绍权限申请时,我们调用的是ActivityCompat的requestPermissions方法,接下来我们跟随源码,看系统是如何完成权限申请的。

ActivityCompat # requestPermissions

public static void requestPermissions(final @NonNull Activity activity,
       final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
   if (sDelegate != null
           && sDelegate.requestPermissions(activity, permissions, requestCode)) {
       // Delegate has handled the permission request.
       return;
   }

   for (String permission : permissions) {
       if (TextUtils.isEmpty(permission)) {
           throw new IllegalArgumentException("Permission request for permissions "
                   + Arrays.toString(permissions) + " must not contain null or empty values");
       }
   }

   if (Build.VERSION.SDK_INT >= 23) {
       if (activity instanceof RequestPermissionsRequestCodeValidator) {
           ((RequestPermissionsRequestCodeValidator) activity)
                   .validateRequestPermissionsRequestCode(requestCode);
       }
       Api23Impl.requestPermissions(activity, permissions, requestCode);
   } else if (activity instanceof OnRequestPermissionsResultCallback) {
       Handler handler = new Handler(Looper.getMainLooper());
       handler.post(new Runnable() {
           @Override
           public void run() {
               final int[] grantResults = new int[permissions.length];

               PackageManager packageManager = activity.getPackageManager();
               String packageName = activity.getPackageName();

               final int permissionCount = permissions.length;
               for (int i = 0; i < permissionCount; i++) {
                   grantResults[i] = packageManager.checkPermission(
                           permissions[i], packageName);
               }

               ((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(
                       requestCode, permissions, grantResults);
           }
       });
   }
}

首先在requestPermissions方法中,判断了SDK的版本,如果大于等于23,也就是包括Android 6.0以上的版本,会执行对应的逻辑,在Android 6.0版本以下,则是直接通过PKMS来检查权限,并做onRequestPermissionsResult的回调,我们关注的重点不在这里,我们重点看Android 6.0以上的版本。

Api23Impl # requestPermissions

@DoNotInline
static void requestPermissions(Activity activity, String[] permissions, int requestCode) {
    activity.requestPermissions(permissions, requestCode);
}

通过源码我们看到是调用了Api23Impl的requestPermissions方法,在这个方法内部,直接调用了Activity的requestPermissions方法。

Activity # requestPermissions

public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
    if (requestCode < 0) {
        throw new IllegalArgumentException("requestCode should be >= 0");
    }
    //同一时间只能有一次权限申请,并发问题处理
    if (mHasCurrentPermissionsRequest) {
        Log.w(TAG, "Can request only one set of permissions at a time");
        // Dispatch the callback with empty arrays which means a cancellation.
        onRequestPermissionsResult(requestCode, new String[0], new int[0]);
        return;
    }

    if (!getAttributionSource().getRenouncedPermissions().isEmpty()) {
        final int permissionCount = permissions.length;
        for (int i = 0; i < permissionCount; i++) {
            if (getAttributionSource().getRenouncedPermissions().contains(permissions[i])) {
                throw new IllegalArgumentException("Cannot request renounced permission: "
                        + permissions[i]);
            }
        }
    }

    final Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
    mHasCurrentPermissionsRequest = true;
}

在这个方法中,前面主要做了一些判断,包括requestCode的校验、并发的处理(同一时间只能进行一次权限申请)等,最后通过PackManager创建一个Intent对象,因为需要回调状态到onRequestPermissionsResult,所以通过startActivityForResult方式启动了一个Activity。

PackageManager # buildRequestPermissionsIntent

public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {
   if (ArrayUtils.isEmpty(permissions)) {
      throw new IllegalArgumentException("permission cannot be null or empty");
   }
   Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
   intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions);
   intent.setPackage(getPermissionControllerPackageName());
   return intent;
}

这个方法非常简单,就是创建了一个Intent对象,但是这个Intent对象的具体配置我们需要看一下。

public static final String ACTION_REQUEST_PERMISSIONS =
        "android.content.pm.action.REQUEST_PERMISSIONS";

public static final String EXTRA_REQUEST_PERMISSIONS_NAMES =
        "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";

在创建一个Intent对象之后,将ACTION_REQUEST_PERMISSIONS作为参数传递到Intent构造函数中,意味着这里是创建了一个隐式意图,这里会启动一个系统的页面

因为通常我们在启动一个Activity的时候,通常是一个显示意图,通过setClass将目的地声明在此,而buildRequestPermissionsIntent中则是创建了一个隐式意图,这里的Activity就是我们看到的那个弹窗页面。那么这些Activity是存在哪里呢,其实就是隐式安装器PackageInstaller提供的。

PackageInstaller

这里我简单提一下,PackageInstaller其实也是一个系统应用,看名字应该知道它的作用是用来安装或者卸载应用程序的,除此之外,还可以管理应用程序的权限,在安装应用程序时,它会向用户显示应用程序要求的权限,并允许用户对这些权限进行管理和控制。

所以我们在安装或者卸载应用时,系统出现的弹窗,都是在PackageInstaller app中提供的,前面我们提到的android.content.pm.action.REQUEST_PERMISSIONS这个action,在PackageInstaller中就对应一个页面,我们可以去看下PackageInstaller的清单文件。

<activity android:name="com.android.packageinstaller.permission.ui.GrantPermissionsActivity"
        android:configChanges="keyboardHidden|screenSize"
        android:excludeFromRecents="true"
        android:theme="@style/GrantPermissions"
        android:visibleToInstantApps="true"
        android:inheritShowWhenLocked="true">
    <intent-filter android:priority="1">
        <action android:name="android.content.pm.action.REQUEST_PERMISSIONS" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

所以当我们申请权限时弹出的系统弹窗,就是GrantPermissionsActivity;除此之外,在构建隐式意图时,还把需要申请的权限组作为参数传进去了,所以在GrantPermissionsActivity中,会接收这些数据。

GrantPermissionsActivity

接下来我们分析GrantPermissionsActivity中处理逻辑,这里我把系统源码中GrantPermissionsActivity拷贝了一份,方便分析权限申请的流程。

public class GrantPermissionsActivity extends Activity
        implements GrantPermissionsViewHandler.ResultListener {

    private static final String LOG_TAG = "GrantPermissionsActivity";

    private static final String KEY_REQUEST_ID = GrantPermissionsActivity.class.getName()
            + "_REQUEST_ID";

    public static int NUM_BUTTONS = 5;
    public static int LABEL_ALLOW_BUTTON = 0;
    public static int LABEL_ALLOW_ALWAYS_BUTTON = 1;
    public static int LABEL_ALLOW_FOREGROUND_BUTTON = 2;
    public static int LABEL_DENY_BUTTON = 3;
    public static int LABEL_DENY_AND_DONT_ASK_AGAIN_BUTTON = 4;

    /** Unique Id of a request */
    private long mRequestId;

    private String[] mRequestedPermissions;
    private CharSequence[] mButtonLabels;

    private ArrayMap<Pair<String, Boolean>, GroupState> mRequestGrantPermissionGroups =
            new ArrayMap<>();

    private GrantPermissionsViewHandler mViewHandler;
    private AppPermissions mAppPermissions;

    boolean mResultSet;

    /**
     * Listens for changes to the permission of the app the permissions are currently getting
     * granted to. {@code null} when unregistered.
     */
    private @Nullable PackageManager.OnPermissionsChangedListener mPermissionChangeListener;

    /**
     * Listens for changes to the app the permissions are currently getting granted to. {@code null}
     * when unregistered.
     */
    private @Nullable PackageRemovalMonitor mPackageRemovalMonitor;
    // .....

    /**
     * Report the result of a grant of a permission.
     *
     * @param permission The permission that was granted or denied
     * @param result The permission grant result
     */
    private void reportRequestResult(@NonNull String permission, int result) {
        boolean isImplicit = !ArrayUtils.contains(mRequestedPermissions, permission);

        Log.v(LOG_TAG,
                "Permission grant result requestId=" + mRequestId + " callingUid=" + mCallingUid
                        + " callingPackage=" + mCallingPackage + " permission=" + permission
                        + " isImplicit=" + isImplicit + " result=" + result);

        PermissionControllerStatsLog.write(
                PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED, mRequestId,
                mCallingUid, mCallingPackage, permission, isImplicit, result);
    }

    /**
     * Report the result of a grant of a permission.
     *
     * @param permissions The permissions that were granted or denied
     * @param result The permission grant result
     */
    private void reportRequestResult(@NonNull String[] permissions, int result) {
        for (String permission : permissions) {
            reportRequestResult(permission, result);
        }
    }

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        // ......
        mRequestedPermissions = getIntent().getStringArrayExtra(
                PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES);
        if (mRequestedPermissions == null) {
            mRequestedPermissions = new String[0];
        }
        //......
        
        //这里会通过判断硬件设备类型,展示不同的UI
        if (DeviceUtils.isTelevision(this)) {
            mViewHandler = new com.android.packageinstaller.permission.ui.television
                    .GrantPermissionsViewHandlerImpl(this,
                    mCallingPackage).setResultListener(this);
        } else if (DeviceUtils.isWear(this)) {
            mViewHandler = new GrantPermissionsWatchViewHandler(this).setResultListener(this);
        } else if (DeviceUtils.isAuto(this)) {
            mViewHandler = new GrantPermissionsAutoViewHandler(this, mCallingPackage, userHandle)
                    .setResultListener(this);
        } else {
            mViewHandler = new com.android.packageinstaller.permission.ui.handheld
                    .GrantPermissionsViewHandlerImpl(this, mCallingPackage, userHandle)
                    .setResultListener(this);
        }

        // ......

        setContentView(mViewHandler.createView());

        // ......
    }

    //......

    @Override
    public void onPermissionGrantResult(String name,
            @GrantPermissionsViewHandler.Result int result) {
        logGrantPermissionActivityButtons(name, result);
        GroupState foregroundGroupState = getForegroundGroupState(name);
        GroupState backgroundGroupState = getBackgroundGroupState(name);

        if (result == GRANTED_ALWAYS || result == GRANTED_FOREGROUND_ONLY
                || result == DENIED_DO_NOT_ASK_AGAIN) {
            KeyguardManager kgm = getSystemService(KeyguardManager.class);

            if (kgm.isDeviceLocked()) {
                kgm.requestDismissKeyguard(this, new KeyguardManager.KeyguardDismissCallback() {
                            @Override
                            public void onDismissError() {
                                Log.e(LOG_TAG, "Cannot dismiss keyguard perm=" + name + " result="
                                        + result);
                            }

                            @Override
                            public void onDismissCancelled() {
                                // do nothing (i.e. stay at the current permission group)
                            }

                            @Override
                            public void onDismissSucceeded() {
                                // Now the keyguard is dismissed, hence the device is not locked
                                // anymore
                                onPermissionGrantResult(name, result);
                            }
                        });

                return;
            }
        }

        switch (result) {
            case GRANTED_ALWAYS :
                if (foregroundGroupState != null) {
                    onPermissionGrantResultSingleState(foregroundGroupState, true, false);
                }
                if (backgroundGroupState != null) {
                    onPermissionGrantResultSingleState(backgroundGroupState, true, false);
                }
                break;
            case GRANTED_FOREGROUND_ONLY :
                if (foregroundGroupState != null) {
                    onPermissionGrantResultSingleState(foregroundGroupState, true, false);
                }
                if (backgroundGroupState != null) {
                    onPermissionGrantResultSingleState(backgroundGroupState, false, false);
                }
                break;
            case DENIED :
                if (foregroundGroupState != null) {
                    onPermissionGrantResultSingleState(foregroundGroupState, false, false);
                }
                if (backgroundGroupState != null) {
                    onPermissionGrantResultSingleState(backgroundGroupState, false, false);
                }
                break;
            case DENIED_DO_NOT_ASK_AGAIN :
                if (foregroundGroupState != null) {
                    onPermissionGrantResultSingleState(foregroundGroupState, false, true);
                }
                if (backgroundGroupState != null) {
                    onPermissionGrantResultSingleState(backgroundGroupState, false, true);
                }
                break;
        }

        if (!showNextPermissionGroupGrantRequest()) {
            setResultAndFinish();
        }
    }

    /**
     * Grants or revoked the affected permissions for a single {@link groupState}.
     *
     * @param groupState The group state with the permissions to grant/revoke
     * @param granted {@code true} if the permissions should be granted, {@code false} if they
     *        should be revoked
     * @param doNotAskAgain if the permissions should be revoked should be app be allowed to ask
     *        again for the same permissions?
     */
    private void onPermissionGrantResultSingleState(GroupState groupState, boolean granted,
            boolean doNotAskAgain) {
        if (groupState != null && groupState.mGroup != null
                && groupState.mState == GroupState.STATE_UNKNOWN) {
            if (granted) {
                groupState.mGroup.grantRuntimePermissions(doNotAskAgain,
                        groupState.affectedPermissions);
                groupState.mState = GroupState.STATE_ALLOWED;

                reportRequestResult(groupState.affectedPermissions,
                        PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED);
            } else {
                groupState.mGroup.revokeRuntimePermissions(doNotAskAgain,
                        groupState.affectedPermissions);
                groupState.mState = GroupState.STATE_DENIED;

                reportRequestResult(groupState.affectedPermissions, doNotAskAgain
                        ?
                        PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE
                        : PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED);
            }
        }
    }
    // ......

    private void setResultIfNeeded(int resultCode) {
        if (!mResultSet) {
            mResultSet = true;
            logRequestedPermissionGroups();
            Intent result = new Intent(PackageManager.ACTION_REQUEST_PERMISSIONS);
            result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, mRequestedPermissions);

            PackageManager pm = getPackageManager();
            int numRequestedPermissions = mRequestedPermissions.length;
            int[] grantResults = new int[numRequestedPermissions];
            for (int i = 0; i < numRequestedPermissions; i++) {
                grantResults[i] = pm.checkPermission(mRequestedPermissions[i], mCallingPackage);
            }

            result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, grantResults);
            setResult(resultCode, result);
        }
    }

    private void setResultAndFinish() {
        setResultIfNeeded(RESULT_OK);
        finish();
    }

    // ......

    private static final class GroupState {
        static final int STATE_UNKNOWN = 0;
        static final int STATE_ALLOWED = 1;
        static final int STATE_DENIED = 2;
        static final int STATE_SKIPPED = 3;

        final AppPermissionGroup mGroup;
        int mState = STATE_UNKNOWN;

        /** Permissions of this group that need to be granted, null == no permissions of group */
        String[] affectedPermissions;

        GroupState(AppPermissionGroup group) {
            mGroup = group;
        }
    }

    private class PermissionChangeListener implements PackageManager.OnPermissionsChangedListener {
        final int mCallingPackageUid;

        PermissionChangeListener() throws NameNotFoundException {
            mCallingPackageUid = getPackageManager().getPackageUid(mCallingPackage, 0);
        }

        @Override
        public void onPermissionsChanged(int uid) {
            if (uid == mCallingPackageUid) {
                updateIfPermissionsWereGranted();
            }
        }
    }
}

GrantPermissionsActivity # onPermissionGrantResult

当用户点击按钮时,会调用onPermissionGrantResult方法,在这个方法中,会判断用户行为,一般会有以下几种:始终允许、仅允许一次、禁止、禁止而且不需要再提醒,针对每种结果,都会调用onPermissionGrantResultSingleState方法来具体的实施。

在onPermissionGrantResultSingleState中,会判断granted参数,也就是否允许权限,调用AppPermissionGroup的grantRuntimePermissions方法做具体的运行时权限申请。

AppPermissionGroup # grantRuntimePermissions

注意这个方法中的参数,fixedByTheUser其实对应的就是dontAskAgain,是否需要再次询问,如果用户选择拒绝权限而且不再询问,那么就只能去设置中打开权限。

public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
    boolean killApp = false;
    boolean wasAllGranted = true;

    // We toggle permissions only to apps that support runtime
    // permissions, otherwise we toggle the app op corresponding
    // to the permission if the permission is granted to the app.
    for (Permission permission : mPermissions.values()) {
        if (filterPermissions != null
                && !ArrayUtils.contains(filterPermissions, permission.getName())) {
            continue;
        }

        if (!permission.isGrantingAllowed(mIsEphemeralApp, mAppSupportsRuntimePermissions)) {
            // Skip unallowed permissions.
            continue;
        }

        boolean wasGranted = permission.isGrantedIncludingAppOp();

        if (mAppSupportsRuntimePermissions) {
            // Do not touch permissions fixed by the system.
            if (permission.isSystemFixed()) {
                wasAllGranted = false;
                break;
            }

            // Ensure the permission app op enabled before the permission grant.
            if (permission.affectsAppOp() && !permission.isAppOpAllowed()) {
                permission.setAppOpAllowed(true);
            }

            // Grant the permission if needed.
            if (!permission.isGranted()) {
                permission.setGranted(true);
            }

            // Update the permission flags.
            if (!fixedByTheUser) {
                // Now the apps can ask for the permission as the user
                // no longer has it fixed in a denied state.
                if (permission.isUserFixed() || permission.isUserSet()) {
                    permission.setUserFixed(false);
                    permission.setUserSet(false);
                }
            }
        } else {
            // Legacy apps cannot have a not granted permission but just in case.
            if (!permission.isGranted()) {
                continue;
            }

            // If the permissions has no corresponding app op, then it is a
            // third-party one and we do not offer toggling of such permissions.
            if (permission.affectsAppOp()) {
                if (!permission.isAppOpAllowed()) {
                    permission.setAppOpAllowed(true);

                    // Legacy apps do not know that they have to retry access to a
                    // resource due to changes in runtime permissions (app ops in this
                    // case). Therefore, we restart them on app op change, so they
                    // can pick up the change.
                    killApp = true;
                }

                // Mark that the permission should not be be granted on upgrade
                // when the app begins supporting runtime permissions.
                if (permission.shouldRevokeOnUpgrade()) {
                    permission.setRevokeOnUpgrade(false);
                }
            }

            // Granting a permission explicitly means the user already
            // reviewed it so clear the review flag on every grant.
            if (permission.isReviewRequired()) {
                permission.unsetReviewRequired();
            }
        }

        // If we newly grant background access to the fine location, double-guess the user some
        // time later if this was really the right choice.
        if (!wasGranted && permission.isGrantedIncludingAppOp()) {
            if (permission.getName().equals(ACCESS_FINE_LOCATION)) {
                Permission bgPerm = permission.getBackgroundPermission();
                if (bgPerm != null) {
                    if (bgPerm.isGrantedIncludingAppOp()) {
                        mTriggerLocationAccessCheckOnPersist = true;
                    }
                }
            } else if (permission.getName().equals(ACCESS_BACKGROUND_LOCATION)) {
                ArrayList<Permission> fgPerms = permission.getForegroundPermissions();
                if (fgPerms != null) {
                    int numFgPerms = fgPerms.size();
                    for (int fgPermNum = 0; fgPermNum < numFgPerms; fgPermNum++) {
                        Permission fgPerm = fgPerms.get(fgPermNum);

                        if (fgPerm.getName().equals(ACCESS_FINE_LOCATION)) {
                            if (fgPerm.isGrantedIncludingAppOp()) {
                                mTriggerLocationAccessCheckOnPersist = true;
                            }

                            break;
                        }
                    }
                }
            }
        }
    }
    //默认 mDelayChanges = false,这里真正进行权限申请
    if (!mDelayChanges) {
        persistChanges(false);

        if (killApp) {
            killApp(KILL_REASON_APP_OP_CHANGE);
        }
    }

    return wasAllGranted;
}

前面主要是进行一系列的配置,persistChanges方法中会向PKMS发起进程间通信,调用PKMS的grantRuntimePermission方法。


void persistChanges(boolean mayKillBecauseOfAppOpsChange) {
        int uid = mPackageInfo.applicationInfo.uid;

        int numPermissions = mPermissions.size();
        boolean shouldKillApp = false;

        for (int i = 0; i < numPermissions; i++) {
            Permission permission = mPermissions.valueAt(i);

            if (!permission.isSystemFixed()) {
                if (permission.isGranted()) {
                    mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
                            permission.getName(), mUserHandle);
                } else {
                    boolean isCurrentlyGranted = mContext.checkPermission(permission.getName(), -1,
                            uid) == PERMISSION_GRANTED;

                    if (isCurrentlyGranted) {
                        mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
                                permission.getName(), mUserHandle);
                    }
                }
            }

            int flags = (permission.isUserSet() ? PackageManager.FLAG_PERMISSION_USER_SET : 0)
                    | (permission.isUserFixed() ? PackageManager.FLAG_PERMISSION_USER_FIXED : 0)
                    | (permission.shouldRevokeOnUpgrade()
                    ? PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE : 0)
                    | (permission.isPolicyFixed() ? PackageManager.FLAG_PERMISSION_POLICY_FIXED : 0)
                    | (permission.isReviewRequired()
                    ? PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED : 0);

            mPackageManager.updatePermissionFlags(permission.getName(),
                    mPackageInfo.packageName,
                    PackageManager.FLAG_PERMISSION_USER_SET
                            | PackageManager.FLAG_PERMISSION_USER_FIXED
                            | PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE
                            | PackageManager.FLAG_PERMISSION_POLICY_FIXED
                            | PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED,
                    flags, mUserHandle);

            if (permission.affectsAppOp()) {
                if (!permission.isSystemFixed()) {
                    // Enabling/Disabling an app op may put the app in a situation in which it has
                    // a handle to state it shouldn't have, so we have to kill the app. This matches
                    // the revoke runtime permission behavior.
                    if (permission.isAppOpAllowed()) {
                        shouldKillApp |= allowAppOp(permission, uid);
                    } else {
                        shouldKillApp |= disallowAppOp(permission, uid);
                    }
                }
            }
        }

        if (mayKillBecauseOfAppOpsChange && shouldKillApp) {
            killApp(KILL_REASON_APP_OP_CHANGE);
        }

        if (mTriggerLocationAccessCheckOnPersist) {
            new LocationAccessCheck(mContext, null).checkLocationAccessSoon();
            mTriggerLocationAccessCheckOnPersist = false;
        }
    }

PKMS # grantRuntimePermission

@Override
public void grantRuntimePermission(String packageName, String permName, final int userId) {
    // Because this is accessed via the package manager service AIDL,
    // go through the permission manager service AIDL
    mContext.getSystemService(PermissionManager.class)
            .grantRuntimePermission(packageName, permName, UserHandle.of(userId));
}

这里我们看到,是拿到PermissionManager服务对象,调用PMMS的grantRuntimePermission方法,所以我们需要去PermissionManagerService中查找。

@Override
public void grantRuntimePermission(String packageName, String permName, final int userId) {
    final int callingUid = Binder.getCallingUid();
    final boolean overridePolicy =
            checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY)
                    == PackageManager.PERMISSION_GRANTED;

    grantRuntimePermissionInternal(packageName, permName, overridePolicy,
            callingUid, userId, mDefaultPermissionCallback);
}

private void grantRuntimePermissionInternal(String packageName, String permName,
        boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) {
    // ..... 
    
    final int uid = UserHandle.getUid(userId, pkg.getUid());
    if (callback != null) {
        if (isRuntimePermission) {
            callback.onPermissionGranted(uid, userId);
        } else {
            callback.onInstallPermissionGranted();
        }
        if (permissionHasGids) {
            callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId);
        }
    }

    if (isRuntimePermission) {
        notifyRuntimePermissionStateChanged(packageName, userId);
    }
}

其实在PermissionManagerService的grantRuntimePermissionInternal前面所有的判断,都是在判断当前这个权限是不是已经获取到了,如果获取到了就退出;如果没有获取到,那么就会通过PermissionCallback回调,并判断是否为运行时权限,如果是运行时权限,会回调onPermissionGranted方法,非运行时权限会回调onInstallPermissionGranted方法。

private final PermissionCallback mDefaultPermissionCallback = new PermissionCallback() {
    @Override
    public void onGidsChanged(int appId, int userId) {
        mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED));
    }
    @Override
    public void onPermissionGranted(int uid, int userId) {
        mOnPermissionChangeListeners.onPermissionsChanged(uid);

        // Not critical; if this is lost, the application has to request again.
        mPackageManagerInt.writeSettings(true);
    }
    @Override
    public void onInstallPermissionGranted() {
        mPackageManagerInt.writeSettings(true);
    }
    @Override
    public void onPermissionRevoked(int uid, int userId, String reason) {
        mOnPermissionChangeListeners.onPermissionsChanged(uid);

        // Critical; after this call the application should never have the permission
        mPackageManagerInt.writeSettings(false);
        final int appId = UserHandle.getAppId(uid);
        if (reason == null) {
            mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED));
        } else {
            mHandler.post(() -> killUid(appId, userId, reason));
        }
    }
    @Override
    public void onInstallPermissionRevoked() {
        mPackageManagerInt.writeSettings(true);
    }
    @Override
    public void onPermissionUpdated(int[] userIds, boolean sync) {
        mPackageManagerInt.writePermissionSettings(userIds, !sync);
    }
    @Override
    public void onInstallPermissionUpdated() {
        mPackageManagerInt.writeSettings(true);
    }
    @Override
    public void onPermissionRemoved() {
        mPackageManagerInt.writeSettings(false);
    }
    public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
            int uid) {
        onPermissionUpdated(updatedUserIds, sync);
        for (int i = 0; i < updatedUserIds.length; i++) {
            int userUid = UserHandle.getUid(updatedUserIds[i], UserHandle.getAppId(uid));
            mOnPermissionChangeListeners.onPermissionsChanged(userUid);
        }
    }
    public void onInstallPermissionUpdatedNotifyListener(int uid) {
        onInstallPermissionUpdated();
        mOnPermissionChangeListeners.onPermissionsChanged(uid);
    }
};

在onPermissionGranted回调方法中,会调用PackageManagerInternal的writeSettings方法,将权限信息写入到xml文件中。

PackageManagerInternal # writeSettings

@Override
public void writeSettings(boolean async) {
    synchronized (mLock) {
        if (async) {
            scheduleWriteSettingsLocked();
        } else {
            writeSettingsLPrTEMP();
        }
    }
}

这里是可以选择同步或者异步,因为涉及到了IO操作,所以这里传入的是true。

case WRITE_SETTINGS: {
    Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
    synchronized (mLock) {
        removeMessages(WRITE_SETTINGS);
        removeMessages(WRITE_PACKAGE_RESTRICTIONS);
        writeSettingsLPrTEMP();
        mDirtyUsers.clear();
    }
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
} 
break;

最终是调用writeLPr方法,创建xml文件

void writeLPr() {
    //Debug.startMethodTracing("/data/system/packageprof", 8 * 1024 * 1024);

    final long startTime = SystemClock.uptimeMillis();

    // Whenever package manager changes something on the system, it writes out whatever it
    // changed in the form of a settings object change, and it does so under its internal
    // lock --- so if we invalidate the package cache here, we end up invalidating at the
    // right time.
    invalidatePackageCache();

    // Keep the old settings around until we know the new ones have
    // been successfully written.
    if (mSettingsFilename.exists()) {
        // Presence of backup settings file indicates that we failed
        // to persist settings earlier. So preserve the older
        // backup for future reference since the current settings
        // might have been corrupted.
        if (!mBackupSettingsFilename.exists()) {
            if (!mSettingsFilename.renameTo(mBackupSettingsFilename)) {
                Slog.wtf(PackageManagerService.TAG,
                        "Unable to backup package manager settings, "
                        + " current changes will be lost at reboot");
                return;
            }
        } else {
            mSettingsFilename.delete();
            Slog.w(PackageManagerService.TAG, "Preserving older settings backup");
        }
    }

    mPastSignatures.clear();

    try {
        final FileOutputStream fstr = new FileOutputStream(mSettingsFilename);
        final TypedXmlSerializer serializer = Xml.resolveSerializer(fstr);
        serializer.startDocument(null, true);
        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);

        serializer.startTag(null, "packages");

        for (int i = 0; i < mVersion.size(); i++) {
            final String volumeUuid = mVersion.keyAt(i);
            final VersionInfo ver = mVersion.valueAt(i);

            serializer.startTag(null, TAG_VERSION);
            XmlUtils.writeStringAttribute(serializer, ATTR_VOLUME_UUID, volumeUuid);
            serializer.attributeInt(null, ATTR_SDK_VERSION, ver.sdkVersion);
            serializer.attributeInt(null, ATTR_DATABASE_VERSION, ver.databaseVersion);
            XmlUtils.writeStringAttribute(serializer, ATTR_FINGERPRINT, ver.fingerprint);
            serializer.endTag(null, TAG_VERSION);
        }

        if (mVerifierDeviceIdentity != null) {
            serializer.startTag(null, "verifier");
            serializer.attribute(null, "device", mVerifierDeviceIdentity.toString());
            serializer.endTag(null, "verifier");
        }

        serializer.startTag(null, "permission-trees");
        mPermissions.writePermissionTrees(serializer);
        serializer.endTag(null, "permission-trees");

        serializer.startTag(null, "permissions");
        mPermissions.writePermissions(serializer);
        serializer.endTag(null, "permissions");

        for (final PackageSetting pkg : mPackages.values()) {
            writePackageLPr(serializer, pkg);
        }

        for (final PackageSetting pkg : mDisabledSysPackages.values()) {
            writeDisabledSysPackageLPr(serializer, pkg);
        }

        for (final SharedUserSetting usr : mSharedUsers.values()) {
            serializer.startTag(null, "shared-user");
            serializer.attribute(null, ATTR_NAME, usr.name);
            serializer.attributeInt(null, "userId", usr.userId);
            usr.signatures.writeXml(serializer, "sigs", mPastSignatures.untrackedStorage());
            serializer.endTag(null, "shared-user");
        }

        if (mRenamedPackages.size() > 0) {
            for (Map.Entry<String, String> e : mRenamedPackages.entrySet()) {
                serializer.startTag(null, "renamed-package");
                serializer.attribute(null, "new", e.getKey());
                serializer.attribute(null, "old", e.getValue());
                serializer.endTag(null, "renamed-package");
            }
        }

        mDomainVerificationManager.writeSettings(serializer, false /* includeSignatures */,
                UserHandle.USER_ALL);

        mKeySetManagerService.writeKeySetManagerServiceLPr(serializer);

        serializer.endTag(null, "packages");

        serializer.endDocument();

        fstr.flush();
        FileUtils.sync(fstr);
        fstr.close();

        // New settings successfully written, old ones are no longer
        // needed.
        mBackupSettingsFilename.delete();
        FileUtils.setPermissions(mSettingsFilename.toString(),
                FileUtils.S_IRUSR|FileUtils.S_IWUSR
                |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
                -1, -1);

        writeKernelMappingLPr();
        writePackageListLPr();
        writeAllUsersPackageRestrictionsLPr();
        writeAllRuntimePermissionsLPr();
        com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
                "package", SystemClock.uptimeMillis() - startTime);
        return;

    } catch(java.io.IOException e) {
        Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
                + "current changes will be lost at reboot", e);
    }
    // Clean up partially written files
    if (mSettingsFilename.exists()) {
        if (!mSettingsFilename.delete()) {
            Slog.wtf(PackageManagerService.TAG, "Failed to clean up mangled file: "
                    + mSettingsFilename);
        }
    }
    //Debug.stopMethodTracing();
}

具体文件为:

mSettingsFilename = new File(mSystemDir, "packages.xml");

也就是在data/system/packages.xml文件中永久保存,如果应用卸载那么就会清除权限.

<package name="com.lay.nowinandroid" codePath="/data/app/~~Z3KlFzqUZ3vXhgizpl-R4Q==/com.lay.nowinandroid-NcpT91M9gQfcPkeJJhPoxA==" nativeLibraryPath="/data/app/~~Z3KlFzqUZ3vXhgizpl-R4Q==/com.lay.nowinandroid-NcpT91M9gQfcPkeJJhPoxA==/lib" publicFlags="810073926" privateFlags="-1400893440" ft="188c89ab490" it="188c7e11f01" ut="188c89ab614" version="1" userId="10131">
    <sigs count="1" schemeVersion="2">
        <cert index="9" key="/>
    </sigs>
    <perms>
        <item name="android.permission.INTERNET" granted="true" flags="0" />
    </perms>
    <proper-signing-keyset identifier="10" />
</package>

我们可以看到,在xml文件中的perms标签下有对应权限的声明,以及granted的参数,也就是说下次再进来之后,会检查这个xml文件中我们要申请的这个权限是不是已经获取到了,如果granted = true || flags = 0,那么就不会再弹窗了。

上一篇下一篇

猜你喜欢

热点阅读