• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.permissioncontroller.permission.model;
18 
19 import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
20 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
21 import static android.app.AppOpsManager.MODE_ALLOWED;
22 import static android.app.AppOpsManager.MODE_FOREGROUND;
23 import static android.app.AppOpsManager.MODE_IGNORED;
24 import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
25 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
26 
27 import android.app.ActivityManager;
28 import android.app.AppOpsManager;
29 import android.app.Application;
30 import android.content.Context;
31 import android.content.pm.PackageInfo;
32 import android.content.pm.PackageItemInfo;
33 import android.content.pm.PackageManager;
34 import android.content.pm.PackageManager.NameNotFoundException;
35 import android.content.pm.PermissionGroupInfo;
36 import android.content.pm.PermissionInfo;
37 import android.os.Build;
38 import android.os.UserHandle;
39 import android.permission.PermissionManager;
40 import android.text.TextUtils;
41 import android.util.ArrayMap;
42 import android.util.Log;
43 
44 import androidx.annotation.NonNull;
45 import androidx.annotation.Nullable;
46 import androidx.annotation.StringRes;
47 
48 import com.android.permissioncontroller.R;
49 import com.android.permissioncontroller.permission.service.LocationAccessCheck;
50 import com.android.permissioncontroller.permission.utils.ArrayUtils;
51 import com.android.permissioncontroller.permission.utils.LocationUtils;
52 import com.android.permissioncontroller.permission.utils.SoftRestrictedPermissionPolicy;
53 import com.android.permissioncontroller.permission.utils.Utils;
54 
55 import java.text.Collator;
56 import java.util.ArrayList;
57 import java.util.List;
58 import java.util.Objects;
59 import java.util.Set;
60 
61 /**
62  * All permissions of a permission group that are requested by an app.
63  *
64  * <p>Some permissions only grant access to the protected resource while the app is running in the
65  * foreground. These permissions are considered "split" into this foreground and a matching
66  * "background" permission.
67  *
68  * <p>All background permissions of the group are not in the main group and will not be affected
69  * by operations on the group. The background permissions can be found in the {@link
70  * #getBackgroundPermissions() background permissions group}.
71  */
72 public final class AppPermissionGroup implements Comparable<AppPermissionGroup> {
73     private static final String LOG_TAG = AppPermissionGroup.class.getSimpleName();
74     private static final String PLATFORM_PACKAGE_NAME = "android";
75 
76     private static final String KILL_REASON_APP_OP_CHANGE = "Permission related app op changed";
77 
78     /**
79      * Importance level to define the threshold for whether a package is in a state which resets the
80      * timer on its one-time permission session
81      */
82     private static final int ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER =
83             ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
84 
85     /**
86      * Importance level to define the threshold for whether a package is in a state which keeps its
87      * one-time permission session alive after the timer ends
88      */
89     private static final int ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE =
90             ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
91 
92     private final Context mContext;
93     private final UserHandle mUserHandle;
94     private final PackageManager mPackageManager;
95     private final AppOpsManager mAppOps;
96     private final ActivityManager mActivityManager;
97     private final Collator mCollator;
98 
99     private final PackageInfo mPackageInfo;
100     private final String mName;
101     private final String mDeclaringPackage;
102     private final CharSequence mLabel;
103     private final CharSequence mFullLabel;
104     private final @StringRes int mRequest;
105     private final @StringRes int mRequestDetail;
106     private final @StringRes int mBackgroundRequest;
107     private final @StringRes int mBackgroundRequestDetail;
108     private final @StringRes int mUpgradeRequest;
109     private final @StringRes int mUpgradeRequestDetail;
110     private final CharSequence mDescription;
111     private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>();
112     private final String mIconPkg;
113     private final int mIconResId;
114 
115     /** Delay changes until {@link #persistChanges} is called */
116     private final boolean mDelayChanges;
117 
118     /**
119      * Some permissions are split into foreground and background permission. All non-split and
120      * foreground permissions are in {@link #mPermissions}, all background permissions are in
121      * this field.
122      */
123     private AppPermissionGroup mBackgroundPermissions;
124 
125     private final boolean mAppSupportsRuntimePermissions;
126     private final boolean mIsEphemeralApp;
127     private final boolean mIsNonIsolatedStorage;
128     private boolean mContainsEphemeralPermission;
129     private boolean mContainsPreRuntimePermission;
130 
131     /**
132      * Does this group contain at least one permission that is split into a foreground and
133      * background permission? This does not necessarily mean that the app also requested the
134      * background permission.
135      */
136     private boolean mHasPermissionWithBackgroundMode;
137 
138     /**
139      * Set if {@link LocationAccessCheck#checkLocationAccessSoon()} should be triggered once the
140      * changes are persisted.
141      */
142     private boolean mTriggerLocationAccessCheckOnPersist;
143 
144     /**
145      * Create the app permission group.
146      *
147      * @param context the {@code Context} to retrieve system services.
148      * @param packageInfo package information about the app.
149      * @param permissionName the name of the permission this object represents.
150      * @param delayChanges whether to delay changes until {@link #persistChanges} is called.
151      *
152      * @return the AppPermissionGroup.
153      */
create(Context context, PackageInfo packageInfo, String permissionName, boolean delayChanges)154     public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
155             String permissionName, boolean delayChanges) {
156         PermissionInfo permissionInfo;
157         try {
158             permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0);
159         } catch (PackageManager.NameNotFoundException e) {
160             return null;
161         }
162 
163         if ((permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
164                 != PermissionInfo.PROTECTION_DANGEROUS
165                 || (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0
166                 || (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) != 0) {
167             return null;
168         }
169 
170         String group = Utils.getGroupOfPermission(permissionInfo);
171         PackageItemInfo groupInfo = permissionInfo;
172         if (group != null) {
173             try {
174                 groupInfo = context.getPackageManager().getPermissionGroupInfo(group, 0);
175             } catch (PackageManager.NameNotFoundException e) {
176                 /* ignore */
177             }
178         }
179 
180         List<PermissionInfo> permissionInfos = null;
181         if (groupInfo instanceof PermissionGroupInfo) {
182             try {
183                 permissionInfos = Utils.getPermissionInfosForGroup(context.getPackageManager(),
184                         groupInfo.name);
185             } catch (PackageManager.NameNotFoundException e) {
186                 /* ignore */
187             }
188         }
189 
190         return create(context, packageInfo, groupInfo, permissionInfos, delayChanges);
191     }
192 
193     /**
194      * Create the app permission group.
195      *
196      * @param app the current application
197      * @param packageName the name of the package
198      * @param permissionGroupName the name of the permission group
199      * @param user the user of the package
200      * @param delayChanges whether to delay changes until {@link #persistChanges} is called.
201      *
202      * @return the AppPermissionGroup.
203      */
create(Application app, String packageName, String permissionGroupName, UserHandle user, boolean delayChanges)204     public static AppPermissionGroup create(Application app, String packageName,
205             String permissionGroupName, UserHandle user, boolean delayChanges) {
206         try {
207             PackageInfo packageInfo = Utils.getUserContext(app, user).getPackageManager()
208                     .getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
209             PackageItemInfo groupInfo = Utils.getGroupInfo(permissionGroupName, app);
210             if (groupInfo == null) {
211                 return null;
212             }
213 
214             List<PermissionInfo> permissionInfos = null;
215             if (groupInfo instanceof PermissionGroupInfo) {
216                 permissionInfos = Utils.getPermissionInfosForGroup(app.getPackageManager(),
217                             groupInfo.name);
218             }
219             return create(app, packageInfo, groupInfo, permissionInfos, delayChanges);
220         } catch (PackageManager.NameNotFoundException e) {
221             return null;
222         }
223     }
224 
225     /**
226      * Create the app permission group.
227      *
228      * @param context the {@code Context} to retrieve system services.
229      * @param packageInfo package information about the app.
230      * @param groupInfo the information about the group created.
231      * @param permissionInfos the information about the permissions belonging to the group.
232      * @param delayChanges whether to delay changes until {@link #persistChanges} is called.
233      *
234      * @return the AppPermissionGroup.
235      */
create(Context context, PackageInfo packageInfo, PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos, boolean delayChanges)236     public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
237             PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos, boolean delayChanges) {
238         PackageManager packageManager = context.getPackageManager();
239         CharSequence groupLabel = groupInfo.loadLabel(packageManager);
240         CharSequence fullGroupLabel = groupInfo.loadSafeLabel(packageManager, 0,
241                 TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE);
242         return create(context, packageInfo, groupInfo, permissionInfos, groupLabel,
243                 fullGroupLabel, delayChanges);
244     }
245 
246     /**
247      * Create the app permission group.
248      *
249      * @param context the {@code Context} to retrieve system services.
250      * @param packageInfo package information about the app.
251      * @param groupInfo the information about the group created.
252      * @param permissionInfos the information about the permissions belonging to the group.
253      * @param groupLabel the label of the group.
254      * @param fullGroupLabel the untruncated label of the group.
255      * @param delayChanges whether to delay changes until {@link #persistChanges} is called.
256      *
257      * @return the AppPermissionGroup.
258      */
create(Context context, PackageInfo packageInfo, PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos, CharSequence groupLabel, CharSequence fullGroupLabel, boolean delayChanges)259     public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
260             PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos,
261             CharSequence groupLabel, CharSequence fullGroupLabel, boolean delayChanges) {
262         PackageManager packageManager = context.getPackageManager();
263         UserHandle userHandle = UserHandle.getUserHandleForUid(packageInfo.applicationInfo.uid);
264 
265         if (groupInfo instanceof PermissionInfo) {
266             permissionInfos = new ArrayList<>();
267             permissionInfos.add((PermissionInfo) groupInfo);
268         }
269 
270         if (permissionInfos == null || permissionInfos.isEmpty()) {
271             return null;
272         }
273 
274         AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
275 
276         AppPermissionGroup group = new AppPermissionGroup(context, packageInfo, groupInfo.name,
277                 groupInfo.packageName, groupLabel, fullGroupLabel,
278                 loadGroupDescription(context, groupInfo, packageManager), getRequest(groupInfo),
279                 getRequestDetail(groupInfo), getBackgroundRequest(groupInfo),
280                 getBackgroundRequestDetail(groupInfo), getUpgradeRequest(groupInfo),
281                 getUpgradeRequestDetail(groupInfo), groupInfo.packageName, groupInfo.icon,
282                 userHandle, delayChanges, appOpsManager);
283 
284         final Set<String> exemptedRestrictedPermissions = context.getPackageManager()
285                 .getWhitelistedRestrictedPermissions(packageInfo.packageName,
286                         Utils.FLAGS_PERMISSION_WHITELIST_ALL);
287 
288         // Parse and create permissions reqested by the app
289         ArrayMap<String, Permission> allPermissions = new ArrayMap<>();
290         final int permissionCount = packageInfo.requestedPermissions == null ? 0
291                 : packageInfo.requestedPermissions.length;
292         String packageName = packageInfo.packageName;
293         for (int i = 0; i < permissionCount; i++) {
294             String requestedPermission = packageInfo.requestedPermissions[i];
295 
296             PermissionInfo requestedPermissionInfo = null;
297 
298             for (PermissionInfo permissionInfo : permissionInfos) {
299                 if (requestedPermission.equals(permissionInfo.name)) {
300                     requestedPermissionInfo = permissionInfo;
301                     break;
302                 }
303             }
304 
305             if (requestedPermissionInfo == null) {
306                 continue;
307             }
308 
309             // Collect only runtime permissions.
310             if ((requestedPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
311                     != PermissionInfo.PROTECTION_DANGEROUS) {
312                 continue;
313             }
314 
315             // Don't allow toggling non-platform permission groups for legacy apps via app ops.
316             if (packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1
317                     && !PLATFORM_PACKAGE_NAME.equals(groupInfo.packageName)) {
318                 continue;
319             }
320 
321             final boolean granted = (packageInfo.requestedPermissionsFlags[i]
322                     & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0;
323 
324             final String appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)
325                     ? AppOpsManager.permissionToOp(requestedPermissionInfo.name) : null;
326 
327             final boolean appOpAllowed;
328             if (appOp == null) {
329                 appOpAllowed = false;
330             } else {
331                 int appOpsMode = appOpsManager.unsafeCheckOpRaw(appOp,
332                         packageInfo.applicationInfo.uid, packageName);
333                 appOpAllowed = appOpsMode == MODE_ALLOWED || appOpsMode == MODE_FOREGROUND;
334             }
335 
336             final int flags = packageManager.getPermissionFlags(
337                     requestedPermission, packageName, userHandle);
338 
339             Permission permission = new Permission(requestedPermission, requestedPermissionInfo,
340                     granted, appOp, appOpAllowed, flags);
341 
342             if (requestedPermissionInfo.backgroundPermission != null) {
343                 group.mHasPermissionWithBackgroundMode = true;
344             }
345 
346             allPermissions.put(requestedPermission, permission);
347         }
348 
349         int numPermissions = allPermissions.size();
350         if (numPermissions == 0) {
351             return null;
352         }
353 
354         // Link up foreground and background permissions
355         for (int i = 0; i < allPermissions.size(); i++) {
356             Permission permission = allPermissions.valueAt(i);
357 
358             if (permission.getBackgroundPermissionName() != null) {
359                 Permission backgroundPermission = allPermissions.get(
360                         permission.getBackgroundPermissionName());
361 
362                 if (backgroundPermission != null) {
363                     backgroundPermission.addForegroundPermissions(permission);
364                     permission.setBackgroundPermission(backgroundPermission);
365 
366                     // The background permissions isAppOpAllowed refers to the background state of
367                     // the foregound permission's appOp. Hence we can only set it once we know the
368                     // matching foreground permission.
369                     // @see #allowAppOp
370                     if (context.getSystemService(AppOpsManager.class).unsafeCheckOpRaw(
371                             permission.getAppOp(), packageInfo.applicationInfo.uid,
372                             packageInfo.packageName) == MODE_ALLOWED) {
373                         backgroundPermission.setAppOpAllowed(true);
374                     }
375                 }
376             }
377         }
378 
379         // Add permissions found to this group
380         for (int i = 0; i < numPermissions; i++) {
381             Permission permission = allPermissions.valueAt(i);
382 
383             if ((!permission.isHardRestricted()
384                     || exemptedRestrictedPermissions.contains(permission.getName()))
385                     && (!permission.isSoftRestricted()
386                     || SoftRestrictedPermissionPolicy.shouldShow(packageInfo, permission))) {
387                 if (permission.isBackgroundPermission()) {
388                     if (group.getBackgroundPermissions() == null) {
389                         group.mBackgroundPermissions = new AppPermissionGroup(group.mContext,
390                                 group.getApp(), group.getName(), group.getDeclaringPackage(),
391                                 group.getLabel(), group.getFullLabel(), group.getDescription(),
392                                 group.getRequest(), group.getRequestDetail(),
393                                 group.getBackgroundRequest(), group.getBackgroundRequestDetail(),
394                                 group.getUpgradeRequest(), group.getUpgradeRequestDetail(),
395                                 group.getIconPkg(), group.getIconResId(), group.getUser(),
396                                 delayChanges, appOpsManager);
397                     }
398 
399                     group.getBackgroundPermissions().addPermission(permission);
400                 } else {
401                     group.addPermission(permission);
402                 }
403             }
404         }
405 
406         if (group.getPermissions().isEmpty()) {
407             return null;
408         }
409 
410         return group;
411     }
412 
getRequest(PackageItemInfo group)413     private static @StringRes int getRequest(PackageItemInfo group) {
414         return Utils.getRequest(group.name);
415     }
416 
loadGroupDescription(Context context, PackageItemInfo group, @NonNull PackageManager packageManager)417     private static CharSequence loadGroupDescription(Context context, PackageItemInfo group,
418                                                      @NonNull PackageManager packageManager) {
419         CharSequence description = null;
420         if (group instanceof PermissionGroupInfo) {
421             description = ((PermissionGroupInfo) group).loadDescription(packageManager);
422         } else if (group instanceof PermissionInfo) {
423             description = ((PermissionInfo) group).loadDescription(packageManager);
424         }
425 
426         if (description == null || description.length() <= 0) {
427             description = context.getString(R.string.default_permission_description);
428         }
429 
430         return description;
431     }
432 
AppPermissionGroup(Context context, PackageInfo packageInfo, String name, String declaringPackage, CharSequence label, CharSequence fullLabel, CharSequence description, @StringRes int request, @StringRes int requestDetail, @StringRes int backgroundRequest, @StringRes int backgroundRequestDetail, @StringRes int upgradeRequest, @StringRes int upgradeRequestDetail, String iconPkg, int iconResId, UserHandle userHandle, boolean delayChanges, @NonNull AppOpsManager appOpsManager)433     private AppPermissionGroup(Context context, PackageInfo packageInfo, String name,
434             String declaringPackage, CharSequence label, CharSequence fullLabel,
435             CharSequence description, @StringRes int request, @StringRes int requestDetail,
436             @StringRes int backgroundRequest, @StringRes int backgroundRequestDetail,
437             @StringRes int upgradeRequest, @StringRes int upgradeRequestDetail,
438             String iconPkg, int iconResId, UserHandle userHandle, boolean delayChanges,
439             @NonNull AppOpsManager appOpsManager) {
440         int targetSDK = packageInfo.applicationInfo.targetSdkVersion;
441 
442         mContext = context;
443         mUserHandle = userHandle;
444         mPackageManager = mContext.getPackageManager();
445         mPackageInfo = packageInfo;
446         mAppSupportsRuntimePermissions = targetSDK > Build.VERSION_CODES.LOLLIPOP_MR1;
447         mIsEphemeralApp = packageInfo.applicationInfo.isInstantApp();
448         mAppOps = appOpsManager;
449         mActivityManager = context.getSystemService(ActivityManager.class);
450         mDeclaringPackage = declaringPackage;
451         mName = name;
452         mLabel = label;
453         mFullLabel = fullLabel;
454         mDescription = description;
455         mCollator = Collator.getInstance(
456                 context.getResources().getConfiguration().getLocales().get(0));
457         mRequest = request;
458         mRequestDetail = requestDetail;
459         mBackgroundRequest = backgroundRequest;
460         mBackgroundRequestDetail = backgroundRequestDetail;
461         mUpgradeRequest = upgradeRequest;
462         mUpgradeRequestDetail = upgradeRequestDetail;
463         mDelayChanges = delayChanges;
464         if (iconResId != 0) {
465             mIconPkg = iconPkg;
466             mIconResId = iconResId;
467         } else {
468             mIconPkg = context.getPackageName();
469             mIconResId = R.drawable.ic_perm_device_info;
470         }
471 
472         mIsNonIsolatedStorage = targetSDK < Build.VERSION_CODES.P
473                 || (targetSDK < Build.VERSION_CODES.R
474                 && mAppOps.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE,
475                         packageInfo.applicationInfo.uid, packageInfo.packageName) == MODE_ALLOWED);
476     }
477 
478     public boolean doesSupportRuntimePermissions() {
479         return mAppSupportsRuntimePermissions;
480     }
481 
482     public boolean isGrantingAllowed() {
483         return (!mIsEphemeralApp || mContainsEphemeralPermission)
484                 && (mAppSupportsRuntimePermissions || mContainsPreRuntimePermission);
485     }
486 
487     public boolean isReviewRequired() {
488         if (mAppSupportsRuntimePermissions) {
489             return false;
490         }
491         final int permissionCount = mPermissions.size();
492         for (int i = 0; i < permissionCount; i++) {
493             Permission permission = mPermissions.valueAt(i);
494             if (permission.isReviewRequired()) {
495                 return true;
496             }
497         }
498         return false;
499     }
500 
501     /**
502      * Are any of the permissions in this group user sensitive.
503      *
504      * @return {@code true} if any of the permissions in the group is user sensitive.
505      */
506     public boolean isUserSensitive() {
507         final int permissionCount = mPermissions.size();
508         for (int i = 0; i < permissionCount; i++) {
509             Permission permission = mPermissions.valueAt(i);
510             if (permission.isUserSensitive()) {
511                 return true;
512             }
513         }
514         return false;
515     }
516 
517     public void unsetReviewRequired() {
518         final int permissionCount = mPermissions.size();
519         for (int i = 0; i < permissionCount; i++) {
520             Permission permission = mPermissions.valueAt(i);
521             if (permission.isReviewRequired()) {
522                 permission.unsetReviewRequired();
523             }
524         }
525 
526         if (!mDelayChanges) {
527             persistChanges(false);
528         }
529     }
530 
531     public boolean hasGrantedByDefaultPermission() {
532         final int permissionCount = mPermissions.size();
533         for (int i = 0; i < permissionCount; i++) {
534             Permission permission = mPermissions.valueAt(i);
535             if (permission.isGrantedByDefault()) {
536                 return true;
537             }
538         }
539         return false;
540     }
541 
542     public PackageInfo getApp() {
543         return mPackageInfo;
544     }
545 
546     public String getName() {
547         return mName;
548     }
549 
550     public String getDeclaringPackage() {
551         return mDeclaringPackage;
552     }
553 
554     public String getIconPkg() {
555         return mIconPkg;
556     }
557 
558     public int getIconResId() {
559         return mIconResId;
560     }
561 
562     public CharSequence getLabel() {
563         return mLabel;
564     }
565 
566     /**
567      * Get the full un-ellipsized label of the permission group.
568      *
569      * @return the full label of the group.
570      */
571     public CharSequence getFullLabel() {
572         return mFullLabel;
573     }
574 
575     /**
576      * @hide
577      * @return The resource Id of the request string.
578      */
579     public @StringRes int getRequest() {
580         return mRequest;
581     }
582 
583     /**
584      * Extract the (subtitle) message explaining to the user that the permission is only granted to
585      * the apps running in the foreground.
586      *
587      * @param info The package item info to extract the message from
588      *
589      * @return the message or 0 if unset
590      */
591     private static @StringRes int getRequestDetail(PackageItemInfo info) {
592         return Utils.getRequestDetail(info.name);
593     }
594 
595     /**
596      * Get the (subtitle) message explaining to the user that the permission is only granted to
597      * the apps running in the foreground.
598      *
599      * @return the message or 0 if unset
600      */
601     public @StringRes int getRequestDetail() {
602         return mRequestDetail;
603     }
604 
605     /**
606      * Extract the title of the dialog explaining to the user that the permission is granted while
607      * the app is in background and in foreground.
608      *
609      * @param info The package item info to extract the message from
610      *
611      * @return the message or 0 if unset
612      */
613     private static @StringRes int getBackgroundRequest(PackageItemInfo info) {
614         return Utils.getBackgroundRequest(info.name);
615     }
616 
617     /**
618      * Get the title of the dialog explaining to the user that the permission is granted while
619      * the app is in background and in foreground.
620      *
621      * @return the message or 0 if unset
622      */
623     public @StringRes int getBackgroundRequest() {
624         return mBackgroundRequest;
625     }
626 
627     /**
628      * Extract the (subtitle) message explaining to the user that the she/he is about to allow the
629      * app to have background access.
630      *
631      * @param info The package item info to extract the message from
632      *
633      * @return the message or 0 if unset
634      */
635     private static @StringRes int getBackgroundRequestDetail(PackageItemInfo info) {
636         return Utils.getBackgroundRequestDetail(info.name);
637     }
638 
639     /**
640      * Get the (subtitle) message explaining to the user that the she/he is about to allow the
641      * app to have background access.
642      *
643      * @return the message or 0 if unset
644      */
645     public @StringRes int getBackgroundRequestDetail() {
646         return mBackgroundRequestDetail;
647     }
648 
649     /**
650      * Extract the title of the dialog explaining to the user that the permission, which was
651      * previously only granted for foreground, is granted while the app is in background and in
652      * foreground.
653      *
654      * @param info The package item info to extract the message from
655      *
656      * @return the message or 0 if unset
657      */
658     private static @StringRes int getUpgradeRequest(PackageItemInfo info) {
659         return Utils.getUpgradeRequest(info.name);
660     }
661 
662     /**
663      * Get the title of the dialog explaining to the user that the permission, which was
664      * previously only granted for foreground, is granted while the app is in background and in
665      * foreground.
666      *
667      * @return the message or 0 if unset
668      */
669     public @StringRes int getUpgradeRequest() {
670         return mUpgradeRequest;
671     }
672 
673     /**
674      * Extract the (subtitle) message explaining to the user that the she/he is about to allow the
675      * app to have background access while currently having foreground only.
676      *
677      * @param info The package item info to extract the message from
678      *
679      * @return the message or 0 if unset
680      */
681     private static @StringRes int getUpgradeRequestDetail(PackageItemInfo info) {
682         return Utils.getUpgradeRequestDetail(info.name);
683     }
684 
685     /**
686      * Get the (subtitle) message explaining to the user that the she/he is about to allow the
687      * app to have background access while currently having foreground only.
688      *
689      * @return the message or 0 if unset
690      */
691     public @StringRes int getUpgradeRequestDetail() {
692         return mUpgradeRequestDetail;
693     }
694 
695     public CharSequence getDescription() {
696         return mDescription;
697     }
698 
699     public UserHandle getUser() {
700         return mUserHandle;
701     }
702 
703     public boolean hasPermission(String permission) {
704         return mPermissions.get(permission) != null;
705     }
706 
707     /**
708      * Return a permission if in this group.
709      *
710      * @param permissionName The name of the permission
711      *
712      * @return The permission
713      */
714     public @Nullable Permission getPermission(@NonNull String permissionName) {
715         return mPermissions.get(permissionName);
716     }
717 
718     public boolean areRuntimePermissionsGranted() {
719         return areRuntimePermissionsGranted(null);
720     }
721 
722     public boolean areRuntimePermissionsGranted(String[] filterPermissions) {
723         if (LocationUtils.isLocationGroupAndProvider(mContext, mName, mPackageInfo.packageName)) {
724             return LocationUtils.isLocationEnabled(mContext);
725         }
726         // The permission of the extra location controller package is determined by the status of
727         // the controller package itself.
728         if (LocationUtils.isLocationGroupAndControllerExtraPackage(
729                 mContext, mName, mPackageInfo.packageName)) {
730             return LocationUtils.isExtraLocationControllerPackageEnabled(mContext);
731         }
732         final int permissionCount = mPermissions.size();
733         for (int i = 0; i < permissionCount; i++) {
734             Permission permission = mPermissions.valueAt(i);
735             if (filterPermissions != null
736                     && !ArrayUtils.contains(filterPermissions, permission.getName())) {
737                 continue;
738             }
739             if (permission.isGrantedIncludingAppOp()) {
740                 return true;
741             }
742         }
743         return false;
744     }
745 
746     public boolean grantRuntimePermissions(boolean setByTheUser, boolean fixedByTheUser) {
747         return grantRuntimePermissions(setByTheUser, fixedByTheUser, null);
748     }
749 
750     /**
751      * Set mode of an app-op if needed.
752      *
753      * @param op The op to set
754      * @param uid The uid the app-op belongs to
755      * @param mode The new mode
756      *
757      * @return {@code true} iff app-op was changed
758      */
759     private boolean setAppOpMode(@NonNull String op, int uid, int mode) {
760         int currentMode = mAppOps.unsafeCheckOpRaw(op, uid, mPackageInfo.packageName);
761         if (currentMode == mode) {
762             return false;
763         }
764 
765         mAppOps.setUidMode(op, uid, mode);
766         return true;
767     }
768 
769     /**
770      * Allow the app op for a permission/uid.
771      *
772      * <p>There are three cases:
773      * <dl>
774      * <dt>The permission is not split into foreground/background</dt>
775      * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd>
776      * <dt>The permission is a foreground permission:</dt>
777      * <dd><dl><dt>The background permission permission is granted</dt>
778      * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd>
779      * <dt>The background permission permission is <u>not</u> granted</dt>
780      * <dd>The app op matching the permission will be set to
781      * {@link AppOpsManager#MODE_FOREGROUND}</dd>
782      * </dl></dd>
783      * <dt>The permission is a background permission:</dt>
784      * <dd>All granted foreground permissions for this background permission will be set to
785      * {@link AppOpsManager#MODE_ALLOWED}</dd>
786      * </dl>
787      *
788      * @param permission The permission which has an appOps that should be allowed
789      * @param uid        The uid of the process the app op is for
790      *
791      * @return {@code true} iff app-op was changed
792      */
793     private boolean allowAppOp(Permission permission, int uid) {
794         boolean wasChanged = false;
795 
796         if (permission.isBackgroundPermission()) {
797             ArrayList<Permission> foregroundPermissions = permission.getForegroundPermissions();
798 
799             int numForegroundPermissions = foregroundPermissions.size();
800             for (int i = 0; i < numForegroundPermissions; i++) {
801                 Permission foregroundPermission = foregroundPermissions.get(i);
802                 if (foregroundPermission.isAppOpAllowed()) {
803                     wasChanged |= setAppOpMode(foregroundPermission.getAppOp(), uid, MODE_ALLOWED);
804                 }
805             }
806         } else {
807             if (permission.hasBackgroundPermission()) {
808                 Permission backgroundPermission = permission.getBackgroundPermission();
809 
810                 if (backgroundPermission == null) {
811                     // The app requested a permission that has a background permission but it did
812                     // not request the background permission, hence it can never get background
813                     // access
814                     wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_FOREGROUND);
815                 } else {
816                     if (backgroundPermission.isAppOpAllowed()) {
817                         wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_ALLOWED);
818                     } else {
819                         wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_FOREGROUND);
820                     }
821                 }
822             } else {
823                 wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_ALLOWED);
824             }
825         }
826 
827         return wasChanged;
828     }
829 
830     /**
831      * Kills the app the permissions belong to (and all apps sharing the same uid)
832      *
833      * @param reason The reason why the apps are killed
834      */
835     private void killApp(String reason) {
836         mActivityManager.killUid(mPackageInfo.applicationInfo.uid, reason);
837     }
838 
839     /**
840      * Grant permissions of the group.
841      *
842      * <p>This also automatically grants all app ops for permissions that have app ops.
843      * <p>This does <u>only</u> grant permissions in {@link #mPermissions}, i.e. usually not
844      * the background permissions.
845      *
846      * @param setByTheUser If the user has made the decision. This does not unset the flag
847      * @param fixedByTheUser If the user requested that she/he does not want to be asked again
848      * @param filterPermissions If {@code null} all permissions of the group will be granted.
849      *                          Otherwise only permissions in {@code filterPermissions} will be
850      *                          granted.
851      *
852      * @return {@code true} iff all permissions of this group could be granted.
853      */
854     public boolean grantRuntimePermissions(boolean setByTheUser, boolean fixedByTheUser,
855             String[] filterPermissions) {
856         boolean killApp = false;
857         boolean wasAllGranted = true;
858 
859         // We toggle permissions only to apps that support runtime
860         // permissions, otherwise we toggle the app op corresponding
861         // to the permission if the permission is granted to the app.
862         for (Permission permission : mPermissions.values()) {
863             if (filterPermissions != null
864                     && !ArrayUtils.contains(filterPermissions, permission.getName())) {
865                 continue;
866             }
867 
868             if (!permission.isGrantingAllowed(mIsEphemeralApp, mAppSupportsRuntimePermissions)) {
869                 // Skip unallowed permissions.
870                 continue;
871             }
872 
873             boolean wasGranted = permission.isGrantedIncludingAppOp();
874 
875             if (mAppSupportsRuntimePermissions) {
876                 // Do not touch permissions fixed by the system.
877                 if (permission.isSystemFixed()) {
878                     wasAllGranted = false;
879                     break;
880                 }
881 
882                 // Ensure the permission app op is enabled before the permission grant.
883                 if (permission.affectsAppOp() && !permission.isAppOpAllowed()) {
884                     permission.setAppOpAllowed(true);
885                 }
886 
887                 // Grant the permission if needed.
888                 if (!permission.isGranted()) {
889                     permission.setGranted(true);
890                 }
891 
892                 // Update the permission flags.
893                 if (!fixedByTheUser) {
894                     if (permission.isUserFixed()) {
895                         permission.setUserFixed(false);
896                     }
897                     if (setByTheUser) {
898                         if (!permission.isUserSet()) {
899                             permission.setUserSet(true);
900                         }
901                     }
902                 } else {
903                     if (!permission.isUserFixed()) {
904                         permission.setUserFixed(true);
905                     }
906                     if (permission.isUserSet()) {
907                         permission.setUserSet(false);
908                     }
909                 }
910             } else {
911                 // Legacy apps cannot have a not granted permission but just in case.
912                 if (!permission.isGranted()) {
913                     continue;
914                 }
915 
916                 // If the permissions has no corresponding app op, then it is a
917                 // third-party one and we do not offer toggling of such permissions.
918                 if (permission.affectsAppOp()) {
919                     if (!permission.isAppOpAllowed()) {
920                         permission.setAppOpAllowed(true);
921 
922                         // Legacy apps do not know that they have to retry access to a
923                         // resource due to changes in runtime permissions (app ops in this
924                         // case). Therefore, we restart them on app op change, so they
925                         // can pick up the change.
926                         killApp = true;
927                     }
928 
929                     // Mark that the permission is not kept granted only for compatibility.
930                     if (permission.isRevokedCompat()) {
931                         permission.setRevokedCompat(false);
932                     }
933                 }
934 
935                 // Granting a permission explicitly means the user already
936                 // reviewed it so clear the review flag on every grant.
937                 if (permission.isReviewRequired()) {
938                     permission.unsetReviewRequired();
939                 }
940             }
941 
942             // If we newly grant background access to the fine location, double-guess the user some
943             // time later if this was really the right choice.
944             if (!wasGranted && permission.isGrantedIncludingAppOp()) {
945                 if (permission.getName().equals(ACCESS_FINE_LOCATION)) {
946                     Permission bgPerm = permission.getBackgroundPermission();
947                     if (bgPerm != null) {
948                         if (bgPerm.isGrantedIncludingAppOp()) {
949                             mTriggerLocationAccessCheckOnPersist = true;
950                         }
951                     }
952                 } else if (permission.getName().equals(ACCESS_BACKGROUND_LOCATION)) {
953                     ArrayList<Permission> fgPerms = permission.getForegroundPermissions();
954                     if (fgPerms != null) {
955                         int numFgPerms = fgPerms.size();
956                         for (int fgPermNum = 0; fgPermNum < numFgPerms; fgPermNum++) {
957                             Permission fgPerm = fgPerms.get(fgPermNum);
958 
959                             if (fgPerm.getName().equals(ACCESS_FINE_LOCATION)) {
960                                 if (fgPerm.isGrantedIncludingAppOp()) {
961                                     mTriggerLocationAccessCheckOnPersist = true;
962                                 }
963 
964                                 break;
965                             }
966                         }
967                     }
968                 }
969             }
970         }
971 
972         if (!mDelayChanges) {
973             persistChanges(false);
974 
975             if (killApp) {
976                 killApp(KILL_REASON_APP_OP_CHANGE);
977             }
978         }
979 
980         return wasAllGranted;
981     }
982 
983     public boolean revokeRuntimePermissions(boolean fixedByTheUser) {
984         return revokeRuntimePermissions(fixedByTheUser, null);
985     }
986 
987     /**
988      * Disallow the app op for a permission/uid.
989      *
990      * <p>There are three cases:
991      * <dl>
992      * <dt>The permission is not split into foreground/background</dt>
993      * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd>
994      * <dt>The permission is a foreground permission:</dt>
995      * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd>
996      * <dt>The permission is a background permission:</dt>
997      * <dd>All granted foreground permissions for this background permission will be set to
998      * {@link AppOpsManager#MODE_FOREGROUND}</dd>
999      * </dl>
1000      *
1001      * @param permission The permission which has an appOps that should be disallowed
1002      * @param uid        The uid of the process the app op if for
1003      *
1004      * @return {@code true} iff app-op was changed
1005      */
1006     private boolean disallowAppOp(Permission permission, int uid) {
1007         boolean wasChanged = false;
1008 
1009         if (permission.isBackgroundPermission()) {
1010             ArrayList<Permission> foregroundPermissions = permission.getForegroundPermissions();
1011 
1012             int numForegroundPermissions = foregroundPermissions.size();
1013             for (int i = 0; i < numForegroundPermissions; i++) {
1014                 Permission foregroundPermission = foregroundPermissions.get(i);
1015                 if (foregroundPermission.isAppOpAllowed()) {
1016                     wasChanged |= setAppOpMode(foregroundPermission.getAppOp(), uid,
1017                             MODE_FOREGROUND);
1018                 }
1019             }
1020         } else {
1021             wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_IGNORED);
1022         }
1023 
1024         return wasChanged;
1025     }
1026 
1027     /**
1028      * Revoke permissions of the group.
1029      *
1030      * <p>This also disallows all app ops for permissions that have app ops.
1031      * <p>This does <u>only</u> revoke permissions in {@link #mPermissions}, i.e. usually not
1032      * the background permissions.
1033      *
1034      * @param fixedByTheUser If the user requested that she/he does not want to be asked again
1035      * @param filterPermissions If {@code null} all permissions of the group will be revoked.
1036      *                          Otherwise only permissions in {@code filterPermissions} will be
1037      *                          revoked.
1038      *
1039      * @return {@code true} iff all permissions of this group could be revoked.
1040      */
1041     public boolean revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
1042         boolean killApp = false;
1043         boolean wasAllRevoked = true;
1044 
1045         // We toggle permissions only to apps that support runtime
1046         // permissions, otherwise we toggle the app op corresponding
1047         // to the permission if the permission is granted to the app.
1048         for (Permission permission : mPermissions.values()) {
1049             if (filterPermissions != null
1050                     && !ArrayUtils.contains(filterPermissions, permission.getName())) {
1051                 continue;
1052             }
1053 
1054             // Do not touch permissions fixed by the system.
1055             if (permission.isSystemFixed()) {
1056                 wasAllRevoked = false;
1057                 break;
1058             }
1059 
1060             if (mAppSupportsRuntimePermissions) {
1061                 // Revoke the permission if needed.
1062                 if (permission.isGranted()) {
1063                     permission.setGranted(false);
1064                 }
1065 
1066                 // Update the permission flags.
1067                 if (fixedByTheUser) {
1068                     // Take a note that the user fixed the permission.
1069                     if (permission.isUserSet() || !permission.isUserFixed()) {
1070                         permission.setUserSet(false);
1071                         permission.setUserFixed(true);
1072                     }
1073                 } else {
1074                     if (!permission.isUserSet() || permission.isUserFixed()) {
1075                         permission.setUserSet(true);
1076                         permission.setUserFixed(false);
1077                     }
1078                 }
1079 
1080                 if (permission.affectsAppOp()) {
1081                     permission.setAppOpAllowed(false);
1082                 }
1083             } else {
1084                 // Legacy apps cannot have a non-granted permission but just in case.
1085                 if (!permission.isGranted()) {
1086                     continue;
1087                 }
1088 
1089                 // If the permission has no corresponding app op, then it is a
1090                 // third-party one and we do not offer toggling of such permissions.
1091                 if (permission.affectsAppOp()) {
1092                     if (permission.isAppOpAllowed()) {
1093                         permission.setAppOpAllowed(false);
1094 
1095                         // Disabling an app op may put the app in a situation in which it
1096                         // has a handle to state it shouldn't have, so we have to kill the
1097                         // app. This matches the revoke runtime permission behavior.
1098                         killApp = true;
1099                     }
1100 
1101                     // Mark that the permission is kept granted only for compatibility.
1102                     if (!permission.isRevokedCompat()) {
1103                         permission.setRevokedCompat(true);
1104                     }
1105                 }
1106             }
1107         }
1108 
1109         if (!mDelayChanges) {
1110             persistChanges(false);
1111 
1112             if (killApp) {
1113                 killApp(KILL_REASON_APP_OP_CHANGE);
1114             }
1115         }
1116 
1117         return wasAllRevoked;
1118     }
1119 
1120     /**
1121      * Mark permissions in this group as policy fixed.
1122      *
1123      * @param filterPermissions The permissions to mark
1124      */
1125     public void setPolicyFixed(@NonNull String[] filterPermissions) {
1126         for (String permissionName : filterPermissions) {
1127             Permission permission = mPermissions.get(permissionName);
1128 
1129             if (permission != null) {
1130                 permission.setPolicyFixed(true);
1131             }
1132         }
1133 
1134         if (!mDelayChanges) {
1135             persistChanges(false);
1136         }
1137     }
1138 
1139     /**
1140      * Set the user-fixed flag for all permissions in this group.
1141      *
1142      * @param isUsedFixed if the flag should be set or not
1143      */
1144     public void setUserFixed(boolean isUsedFixed) {
1145         final int permissionCount = mPermissions.size();
1146         for (int i = 0; i < permissionCount; i++) {
1147             Permission permission = mPermissions.valueAt(i);
1148             permission.setUserFixed(isUsedFixed);
1149         }
1150 
1151         if (!mDelayChanges) {
1152             persistChanges(false);
1153         }
1154     }
1155 
1156     /**
1157      * Set the one-time flag for all permissions in this group.
1158      *
1159      * @param isOneTime if the flag should be set or not
1160      */
1161     public void setOneTime(boolean isOneTime) {
1162         final int permissionCount = mPermissions.size();
1163         for (int i = 0; i < permissionCount; i++) {
1164             Permission permission = mPermissions.valueAt(i);
1165             if (!permission.isBackgroundPermission()) {
1166                 permission.setOneTime(isOneTime);
1167             }
1168         }
1169 
1170         if (!mDelayChanges) {
1171             persistChanges(false);
1172         }
1173     }
1174 
1175     /**
1176      * Set the user-set flag for all permissions in this group.
1177      *
1178      * @param isUserSet if the flag should be set or not
1179      */
1180     public void setUserSet(boolean isUserSet) {
1181         final int permissionCount = mPermissions.size();
1182         for (int i = 0; i < permissionCount; i++) {
1183             Permission permission = mPermissions.valueAt(i);
1184             permission.setUserSet(isUserSet);
1185         }
1186 
1187         if (!mDelayChanges) {
1188             persistChanges(false);
1189         }
1190     }
1191 
1192     public ArrayList<Permission> getPermissions() {
1193         return new ArrayList<>(mPermissions.values());
1194     }
1195 
1196     /**
1197      * @return An {@link AppPermissionGroup}-object that contains all background permissions for
1198      * this group.
1199      */
1200     public AppPermissionGroup getBackgroundPermissions() {
1201         return mBackgroundPermissions;
1202     }
1203 
1204     /**
1205      * @return {@code true} iff the app request at least one permission in this group that has a
1206      * background permission. It is possible that the app does not request the matching background
1207      * permission and hence will only ever get foreground access, never background access.
1208      */
1209     public boolean hasPermissionWithBackgroundMode() {
1210         return mHasPermissionWithBackgroundMode;
1211     }
1212 
1213     /**
1214      * Is the group a storage permission group that is referring to an app that does not have
1215      * isolated storage
1216      *
1217      * @return {@code true} iff this is a storage group on an app that does not have isolated
1218      * storage
1219      */
1220     public boolean isNonIsolatedStorage() {
1221         return mIsNonIsolatedStorage;
1222     }
1223 
1224     /**
1225      * Whether this is group that contains all the background permission for regular permission
1226      * group.
1227      *
1228      * @return {@code true} iff this is a background permission group.
1229      *
1230      * @see #getBackgroundPermissions()
1231      */
1232     public boolean isBackgroundGroup() {
1233         return mPermissions.valueAt(0).isBackgroundPermission();
1234     }
1235 
1236     /**
1237      * Whether this group supports one-time permissions
1238      * @return {@code true} iff this group supports one-time permissions
1239      */
1240     public boolean supportsOneTimeGrant() {
1241         return Utils.supportsOneTimeGrant(getName());
1242     }
1243 
1244     public int getFlags() {
1245         int flags = 0;
1246         final int permissionCount = mPermissions.size();
1247         for (int i = 0; i < permissionCount; i++) {
1248             Permission permission = mPermissions.valueAt(i);
1249             flags |= permission.getFlags();
1250         }
1251         return flags;
1252     }
1253 
1254     public boolean isUserFixed() {
1255         final int permissionCount = mPermissions.size();
1256         for (int i = 0; i < permissionCount; i++) {
1257             Permission permission = mPermissions.valueAt(i);
1258             if (permission.isUserFixed()) {
1259                 return true;
1260             }
1261         }
1262         return false;
1263     }
1264 
1265     public boolean isPolicyFixed() {
1266         final int permissionCount = mPermissions.size();
1267         for (int i = 0; i < permissionCount; i++) {
1268             Permission permission = mPermissions.valueAt(i);
1269             if (permission.isPolicyFixed()) {
1270                 return true;
1271             }
1272         }
1273         return false;
1274     }
1275 
1276     public boolean isUserSet() {
1277         final int permissionCount = mPermissions.size();
1278         for (int i = 0; i < permissionCount; i++) {
1279             Permission permission = mPermissions.valueAt(i);
1280             if (permission.isUserSet()) {
1281                 return true;
1282             }
1283         }
1284         return false;
1285     }
1286 
1287     public boolean isSystemFixed() {
1288         final int permissionCount = mPermissions.size();
1289         for (int i = 0; i < permissionCount; i++) {
1290             Permission permission = mPermissions.valueAt(i);
1291             if (permission.isSystemFixed()) {
1292                 return true;
1293             }
1294         }
1295         return false;
1296     }
1297 
1298     /**
1299      * @return Whether any of the permissions in this group is one-time
1300      */
1301     public boolean isOneTime() {
1302         final int permissionCount = mPermissions.size();
1303         for (int i = 0; i < permissionCount; i++) {
1304             Permission permission = mPermissions.valueAt(i);
1305             if (permission.isOneTime()) {
1306                 return true;
1307             }
1308         }
1309         return false;
1310     }
1311 
1312     @Override
1313     public int compareTo(AppPermissionGroup another) {
1314         final int result = mCollator.compare(mLabel.toString(), another.mLabel.toString());
1315         if (result == 0) {
1316             // Unbadged before badged.
1317             return mPackageInfo.applicationInfo.uid
1318                     - another.mPackageInfo.applicationInfo.uid;
1319         }
1320         return result;
1321     }
1322 
1323     @Override
1324     public boolean equals(Object o) {
1325         if (!(o instanceof AppPermissionGroup)) {
1326             return false;
1327         }
1328 
1329         AppPermissionGroup other = (AppPermissionGroup) o;
1330 
1331         boolean equal = mName.equals(other.mName)
1332                 && mPackageInfo.packageName.equals(other.mPackageInfo.packageName)
1333                 && mUserHandle.equals(other.mUserHandle)
1334                 && mPermissions.equals(other.mPermissions);
1335         if (!equal) {
1336             return false;
1337         }
1338 
1339         if (mBackgroundPermissions != null && other.getBackgroundPermissions() != null) {
1340             return mBackgroundPermissions.getPermissions().equals(
1341                     other.getBackgroundPermissions().getPermissions());
1342         }
1343         return mBackgroundPermissions == other.getBackgroundPermissions();
1344     }
1345 
1346     @Override
1347     public int hashCode() {
1348         ArrayList<Permission> backgroundPermissions = new ArrayList<>();
1349         if (mBackgroundPermissions != null) {
1350             backgroundPermissions = mBackgroundPermissions.getPermissions();
1351         }
1352         return Objects.hash(mName, mPackageInfo.packageName, mUserHandle, mPermissions,
1353                 backgroundPermissions);
1354     }
1355 
1356     @Override
1357     public String toString() {
1358         StringBuilder builder = new StringBuilder();
1359         builder.append(getClass().getSimpleName());
1360         builder.append("{name=").append(mName);
1361         if (mBackgroundPermissions != null) {
1362             builder.append(", <has background permissions>}");
1363         }
1364         if (!mPermissions.isEmpty()) {
1365             builder.append(", <has permissions>}");
1366         } else {
1367             builder.append('}');
1368         }
1369         return builder.toString();
1370     }
1371 
1372     private void addPermission(Permission permission) {
1373         mPermissions.put(permission.getName(), permission);
1374         if (permission.isEphemeral()) {
1375             mContainsEphemeralPermission = true;
1376         }
1377         if (!permission.isRuntimeOnly()) {
1378             mContainsPreRuntimePermission = true;
1379         }
1380     }
1381 
1382     /**
1383      * If the changes to this group were delayed, persist them to the platform.
1384      *
1385      * @param mayKillBecauseOfAppOpsChange If the app these permissions belong to may be killed if
1386      *                                     app ops change. If this is set to {@code false} the
1387      *                                     caller has to make sure to kill the app if needed.
1388      */
1389     public void persistChanges(boolean mayKillBecauseOfAppOpsChange) {
1390         persistChanges(mayKillBecauseOfAppOpsChange, null);
1391     }
1392 
1393 
1394     /**
1395      * If the changes to this group were delayed, persist them to the platform.
1396      *
1397      * @param mayKillBecauseOfAppOpsChange If the app these permissions belong to may be killed if
1398      *                                     app ops change. If this is set to {@code false} the
1399      *                                     caller has to make sure to kill the app if needed.
1400      * @param revokeReason If any permissions are getting revoked, the reason for revoking them.
1401      */
1402     public void persistChanges(boolean mayKillBecauseOfAppOpsChange, String revokeReason) {
1403         int uid = mPackageInfo.applicationInfo.uid;
1404 
1405         int numPermissions = mPermissions.size();
1406         boolean shouldKillApp = false;
1407 
1408         for (int i = 0; i < numPermissions; i++) {
1409             Permission permission = mPermissions.valueAt(i);
1410 
1411             if (!permission.isSystemFixed()) {
1412                 if (permission.isGranted()) {
1413                     mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
1414                             permission.getName(), mUserHandle);
1415                 } else {
1416                     boolean isCurrentlyGranted = mContext.checkPermission(permission.getName(), -1,
1417                             uid) == PERMISSION_GRANTED;
1418 
1419                     if (isCurrentlyGranted) {
1420                         if (revokeReason == null) {
1421                             mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
1422                                     permission.getName(), mUserHandle);
1423                         } else {
1424                             mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
1425                                     permission.getName(), mUserHandle, revokeReason);
1426                         }
1427                     }
1428                 }
1429             }
1430 
1431             int flags = (permission.isUserSet() ? PackageManager.FLAG_PERMISSION_USER_SET : 0)
1432                     | (permission.isUserFixed() ? PackageManager.FLAG_PERMISSION_USER_FIXED : 0)
1433                     | (permission.isRevokedCompat()
1434                     ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0)
1435                     | (permission.isPolicyFixed() ? PackageManager.FLAG_PERMISSION_POLICY_FIXED : 0)
1436                     | (permission.isReviewRequired()
1437                     ? PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED : 0)
1438                     | (permission.isOneTime() ? PackageManager.FLAG_PERMISSION_ONE_TIME : 0);
1439 
1440             mPackageManager.updatePermissionFlags(permission.getName(),
1441                     mPackageInfo.packageName,
1442                     PackageManager.FLAG_PERMISSION_USER_SET
1443                             | PackageManager.FLAG_PERMISSION_USER_FIXED
1444                             | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
1445                             | PackageManager.FLAG_PERMISSION_POLICY_FIXED
1446                             | (permission.isReviewRequired()
1447                             ? 0 : PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED)
1448                             | PackageManager.FLAG_PERMISSION_ONE_TIME
1449                             | PackageManager.FLAG_PERMISSION_AUTO_REVOKED, // clear auto revoke
1450                     flags, mUserHandle);
1451 
1452             if (permission.affectsAppOp()) {
1453                 if (!permission.isSystemFixed()) {
1454                     // Enabling/Disabling an app op may put the app in a situation in which it has
1455                     // a handle to state it shouldn't have, so we have to kill the app. This matches
1456                     // the revoke runtime permission behavior.
1457                     if (permission.isAppOpAllowed()) {
1458                         boolean wasChanged = allowAppOp(permission, uid);
1459                         shouldKillApp |= wasChanged && !mAppSupportsRuntimePermissions;
1460                     } else {
1461                         shouldKillApp |= disallowAppOp(permission, uid);
1462                     }
1463                 }
1464             }
1465         }
1466 
1467         if (mayKillBecauseOfAppOpsChange && shouldKillApp) {
1468             killApp(KILL_REASON_APP_OP_CHANGE);
1469         }
1470 
1471         if (mTriggerLocationAccessCheckOnPersist) {
1472             new LocationAccessCheck(mContext, null).checkLocationAccessSoon();
1473             mTriggerLocationAccessCheckOnPersist = false;
1474         }
1475 
1476         String packageName = mPackageInfo.packageName;
1477         if (isOneTime() && areRuntimePermissionsGranted()) {
1478             mContext.getSystemService(PermissionManager.class)
1479                     .startOneTimePermissionSession(packageName,
1480                             Utils.getOneTimePermissionsTimeout(),
1481                             ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER,
1482                             ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE);
1483         } else if (!Utils.hasOneTimePermissions(mContext, packageName)) {
1484             mContext.getSystemService(PermissionManager.class)
1485                     .stopOneTimePermissionSession(packageName);
1486         }
1487     }
1488 
1489     /**
1490      * Check if permission group contains a runtime permission that split from an installed
1491      * permission and the split happened in an Android version higher than app's targetSdk.
1492      *
1493      * @return {@code true} if there is such permission, {@code false} otherwise
1494      */
1495     public boolean hasInstallToRuntimeSplit() {
1496         PermissionManager permissionManager =
1497                 (PermissionManager) mContext.getSystemService(PermissionManager.class);
1498 
1499         int numSplitPerms = permissionManager.getSplitPermissions().size();
1500         for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
1501             PermissionManager.SplitPermissionInfo spi =
1502                     permissionManager.getSplitPermissions().get(splitPermNum);
1503             String splitPerm = spi.getSplitPermission();
1504 
1505             PermissionInfo pi;
1506             try {
1507                 pi = mPackageManager.getPermissionInfo(splitPerm, 0);
1508             } catch (NameNotFoundException e) {
1509                 Log.w(LOG_TAG, "No such permission: " + splitPerm, e);
1510                 continue;
1511             }
1512 
1513             // Skip if split permission is not "install" permission.
1514             if (pi.getProtection() != pi.PROTECTION_NORMAL) {
1515                 continue;
1516             }
1517 
1518             List<String> newPerms = spi.getNewPermissions();
1519             int numNewPerms = newPerms.size();
1520             for (int newPermNum = 0; newPermNum < numNewPerms; newPermNum++) {
1521                 String newPerm = newPerms.get(newPermNum);
1522 
1523                 if (!hasPermission(newPerm)) {
1524                     continue;
1525                 }
1526 
1527                 try {
1528                     pi = mPackageManager.getPermissionInfo(newPerm, 0);
1529                 } catch (NameNotFoundException e) {
1530                     Log.w(LOG_TAG, "No such permission: " + newPerm, e);
1531                     continue;
1532                 }
1533 
1534                 // Skip if new permission is not "runtime" permission.
1535                 if (pi.getProtection() != pi.PROTECTION_DANGEROUS) {
1536                     continue;
1537                 }
1538 
1539                 if (mPackageInfo.applicationInfo.targetSdkVersion < spi.getTargetSdk()) {
1540                     return true;
1541                 }
1542             }
1543         }
1544         return false;
1545     }
1546 }
1547