• 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.packageinstaller.permission.model;
18 
19 import android.annotation.StringRes;
20 import android.annotation.SystemApi;
21 import android.app.ActivityManager;
22 import android.app.AppOpsManager;
23 import android.content.Context;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageItemInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PermissionGroupInfo;
28 import android.content.pm.PermissionInfo;
29 import android.os.Build;
30 import android.os.Process;
31 import android.os.UserHandle;
32 import android.util.ArrayMap;
33 
34 import com.android.packageinstaller.R;
35 import com.android.packageinstaller.permission.utils.ArrayUtils;
36 import com.android.packageinstaller.permission.utils.LocationUtils;
37 
38 import java.text.Collator;
39 import java.util.ArrayList;
40 import java.util.List;
41 
42 public final class AppPermissionGroup implements Comparable<AppPermissionGroup> {
43     private static final String PLATFORM_PACKAGE_NAME = "android";
44 
45     private static final String KILL_REASON_APP_OP_CHANGE = "Permission related app op changed";
46 
47     private final Context mContext;
48     private final UserHandle mUserHandle;
49     private final PackageManager mPackageManager;
50     private final AppOpsManager mAppOps;
51     private final ActivityManager mActivityManager;
52     private final Collator mCollator;
53 
54     private final PackageInfo mPackageInfo;
55     private final String mName;
56     private final String mDeclaringPackage;
57     private final CharSequence mLabel;
58     private final @StringRes int mRequest;
59     private final CharSequence mDescription;
60     private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>();
61     private final String mIconPkg;
62     private final int mIconResId;
63 
64     private final boolean mAppSupportsRuntimePermissions;
65     private final boolean mIsEphemeralApp;
66     private boolean mContainsEphemeralPermission;
67     private boolean mContainsPreRuntimePermission;
68 
create(Context context, PackageInfo packageInfo, String permissionName)69     public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
70             String permissionName) {
71         PermissionInfo permissionInfo;
72         try {
73             permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0);
74         } catch (PackageManager.NameNotFoundException e) {
75             return null;
76         }
77 
78         if ((permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
79                 != PermissionInfo.PROTECTION_DANGEROUS
80                 || (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0
81                 || (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) != 0) {
82             return null;
83         }
84 
85         PackageItemInfo groupInfo = permissionInfo;
86         if (permissionInfo.group != null) {
87             try {
88                 groupInfo = context.getPackageManager().getPermissionGroupInfo(
89                         permissionInfo.group, 0);
90             } catch (PackageManager.NameNotFoundException e) {
91                 /* ignore */
92             }
93         }
94 
95         List<PermissionInfo> permissionInfos = null;
96         if (groupInfo instanceof PermissionGroupInfo) {
97             try {
98                 permissionInfos = context.getPackageManager().queryPermissionsByGroup(
99                         groupInfo.name, 0);
100             } catch (PackageManager.NameNotFoundException e) {
101                 /* ignore */
102             }
103         }
104 
105         return create(context, packageInfo, groupInfo, permissionInfos,
106                 Process.myUserHandle());
107     }
108 
create(Context context, PackageInfo packageInfo, PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos, UserHandle userHandle)109     public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
110             PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos,
111             UserHandle userHandle) {
112 
113         AppPermissionGroup group = new AppPermissionGroup(context, packageInfo, groupInfo.name,
114                 groupInfo.packageName, groupInfo.loadLabel(context.getPackageManager()),
115                 loadGroupDescription(context, groupInfo), getRequest(groupInfo),
116                 groupInfo.packageName, groupInfo.icon, userHandle);
117 
118         if (groupInfo instanceof PermissionInfo) {
119             permissionInfos = new ArrayList<>();
120             permissionInfos.add((PermissionInfo) groupInfo);
121         }
122 
123         if (permissionInfos == null || permissionInfos.isEmpty()) {
124             return null;
125         }
126 
127         final int permissionCount = packageInfo.requestedPermissions.length;
128         for (int i = 0; i < permissionCount; i++) {
129             String requestedPermission = packageInfo.requestedPermissions[i];
130 
131             PermissionInfo requestedPermissionInfo = null;
132 
133             for (PermissionInfo permissionInfo : permissionInfos) {
134                 if (requestedPermission.equals(permissionInfo.name)) {
135                     requestedPermissionInfo = permissionInfo;
136                     break;
137                 }
138             }
139 
140             if (requestedPermissionInfo == null) {
141                 continue;
142             }
143 
144             // Collect only runtime permissions.
145             if ((requestedPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
146                     != PermissionInfo.PROTECTION_DANGEROUS) {
147                 continue;
148             }
149 
150             // Don't allow toggling non-platform permission groups for legacy apps via app ops.
151             if (packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1
152                     && !PLATFORM_PACKAGE_NAME.equals(groupInfo.packageName)) {
153                 continue;
154             }
155 
156             final boolean granted = (packageInfo.requestedPermissionsFlags[i]
157                     & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0;
158 
159             final String appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)
160                     ? AppOpsManager.permissionToOp(requestedPermissionInfo.name) : null;
161 
162             final boolean appOpAllowed = appOp != null
163                     && context.getSystemService(AppOpsManager.class).checkOpNoThrow(appOp,
164                     packageInfo.applicationInfo.uid, packageInfo.packageName)
165                     == AppOpsManager.MODE_ALLOWED;
166 
167             final int flags = context.getPackageManager().getPermissionFlags(
168                     requestedPermission, packageInfo.packageName, userHandle);
169 
170             Permission permission = new Permission(requestedPermission, granted,
171                     appOp, appOpAllowed, flags, requestedPermissionInfo.protectionLevel);
172             group.addPermission(permission);
173         }
174 
175         return group;
176     }
177 
getRequest(PackageItemInfo group)178     private static @StringRes int getRequest(PackageItemInfo group) {
179         if (group instanceof PermissionGroupInfo) {
180             return ((PermissionGroupInfo) group).requestRes;
181         } else if (group instanceof PermissionInfo) {
182             return ((PermissionInfo) group).requestRes;
183         } else {
184             return 0;
185         }
186     }
187 
loadGroupDescription(Context context, PackageItemInfo group)188     private static CharSequence loadGroupDescription(Context context, PackageItemInfo group) {
189         CharSequence description = null;
190         if (group instanceof PermissionGroupInfo) {
191             description = ((PermissionGroupInfo) group).loadDescription(
192                     context.getPackageManager());
193         } else if (group instanceof PermissionInfo) {
194             description = ((PermissionInfo) group).loadDescription(
195                     context.getPackageManager());
196         }
197 
198         if (description == null || description.length() <= 0) {
199             description = context.getString(R.string.default_permission_description);
200         }
201 
202         return description;
203     }
204 
AppPermissionGroup(Context context, PackageInfo packageInfo, String name, String declaringPackage, CharSequence label, CharSequence description, @StringRes int request, String iconPkg, int iconResId, UserHandle userHandle)205     private AppPermissionGroup(Context context, PackageInfo packageInfo, String name,
206             String declaringPackage, CharSequence label, CharSequence description,
207             @StringRes int request, String iconPkg, int iconResId, UserHandle userHandle) {
208         mContext = context;
209         mUserHandle = userHandle;
210         mPackageManager = mContext.getPackageManager();
211         mPackageInfo = packageInfo;
212         mAppSupportsRuntimePermissions = packageInfo.applicationInfo
213                 .targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
214         mIsEphemeralApp = packageInfo.applicationInfo.isInstantApp();
215         mAppOps = context.getSystemService(AppOpsManager.class);
216         mActivityManager = context.getSystemService(ActivityManager.class);
217         mDeclaringPackage = declaringPackage;
218         mName = name;
219         mLabel = label;
220         mDescription = description;
221         mCollator = Collator.getInstance(
222                 context.getResources().getConfiguration().getLocales().get(0));
223         mRequest = request;
224         if (iconResId != 0) {
225             mIconPkg = iconPkg;
226             mIconResId = iconResId;
227         } else {
228             mIconPkg = context.getPackageName();
229             mIconResId = R.drawable.ic_perm_device_info;
230         }
231     }
232 
doesSupportRuntimePermissions()233     public boolean doesSupportRuntimePermissions() {
234         return mAppSupportsRuntimePermissions;
235     }
236 
isGrantingAllowed()237     public boolean isGrantingAllowed() {
238         return (!mIsEphemeralApp || mContainsEphemeralPermission)
239                 && (mAppSupportsRuntimePermissions || mContainsPreRuntimePermission);
240     }
241 
isReviewRequired()242     public boolean isReviewRequired() {
243         if (mAppSupportsRuntimePermissions) {
244             return false;
245         }
246         final int permissionCount = mPermissions.size();
247         for (int i = 0; i < permissionCount; i++) {
248             Permission permission = mPermissions.valueAt(i);
249             if (permission.isReviewRequired()) {
250                 return true;
251             }
252         }
253         return false;
254     }
255 
resetReviewRequired()256     public void resetReviewRequired() {
257         final int permissionCount = mPermissions.size();
258         for (int i = 0; i < permissionCount; i++) {
259             Permission permission = mPermissions.valueAt(i);
260             if (permission.isReviewRequired()) {
261                 permission.resetReviewRequired();
262                 mPackageManager.updatePermissionFlags(permission.getName(),
263                         mPackageInfo.packageName,
264                         PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED,
265                         0, mUserHandle);
266             }
267         }
268     }
269 
hasGrantedByDefaultPermission()270     public boolean hasGrantedByDefaultPermission() {
271         final int permissionCount = mPermissions.size();
272         for (int i = 0; i < permissionCount; i++) {
273             Permission permission = mPermissions.valueAt(i);
274             if (permission.isGrantedByDefault()) {
275                 return true;
276             }
277         }
278         return false;
279     }
280 
getApp()281     public PackageInfo getApp() {
282         return mPackageInfo;
283     }
284 
getName()285     public String getName() {
286         return mName;
287     }
288 
getDeclaringPackage()289     public String getDeclaringPackage() {
290         return mDeclaringPackage;
291     }
292 
getIconPkg()293     public String getIconPkg() {
294         return mIconPkg;
295     }
296 
getIconResId()297     public int getIconResId() {
298         return mIconResId;
299     }
300 
getLabel()301     public CharSequence getLabel() {
302         return mLabel;
303     }
304 
305     /**
306      * @hide
307      * @return The resource Id of the request string.
308      */
309     @SystemApi
getRequest()310     public @StringRes int getRequest() {
311         return mRequest;
312     }
313 
getDescription()314     public CharSequence getDescription() {
315         return mDescription;
316     }
317 
getUserId()318     public int getUserId() {
319         return mUserHandle.getIdentifier();
320     }
321 
hasPermission(String permission)322     public boolean hasPermission(String permission) {
323         return mPermissions.get(permission) != null;
324     }
325 
areRuntimePermissionsGranted()326     public boolean areRuntimePermissionsGranted() {
327         return areRuntimePermissionsGranted(null);
328     }
329 
areRuntimePermissionsGranted(String[] filterPermissions)330     public boolean areRuntimePermissionsGranted(String[] filterPermissions) {
331         if (LocationUtils.isLocationGroupAndProvider(mName, mPackageInfo.packageName)) {
332             return LocationUtils.isLocationEnabled(mContext);
333         }
334         final int permissionCount = mPermissions.size();
335         for (int i = 0; i < permissionCount; i++) {
336             Permission permission = mPermissions.valueAt(i);
337             if (filterPermissions != null
338                     && !ArrayUtils.contains(filterPermissions, permission.getName())) {
339                 continue;
340             }
341             if (mAppSupportsRuntimePermissions) {
342                 if (permission.isGranted()) {
343                     return true;
344                 }
345             } else if (permission.isGranted() && (permission.getAppOp() == null
346                     || permission.isAppOpAllowed()) && !permission.isReviewRequired()) {
347                 return true;
348             }
349         }
350         return false;
351     }
352 
grantRuntimePermissions(boolean fixedByTheUser)353     public boolean grantRuntimePermissions(boolean fixedByTheUser) {
354         return grantRuntimePermissions(fixedByTheUser, null);
355     }
356 
grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions)357     public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
358         final int uid = mPackageInfo.applicationInfo.uid;
359 
360         // We toggle permissions only to apps that support runtime
361         // permissions, otherwise we toggle the app op corresponding
362         // to the permission if the permission is granted to the app.
363         for (Permission permission : mPermissions.values()) {
364             if (filterPermissions != null
365                     && !ArrayUtils.contains(filterPermissions, permission.getName())) {
366                 continue;
367             }
368 
369             if (!permission.isGrantingAllowed(mIsEphemeralApp, mAppSupportsRuntimePermissions)) {
370                 // Skip unallowed permissions.
371                 continue;
372             }
373 
374             if (mAppSupportsRuntimePermissions) {
375                 // Do not touch permissions fixed by the system.
376                 if (permission.isSystemFixed()) {
377                     return false;
378                 }
379 
380                 // Ensure the permission app op enabled before the permission grant.
381                 if (permission.hasAppOp() && !permission.isAppOpAllowed()) {
382                     permission.setAppOpAllowed(true);
383                     mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
384                 }
385 
386                 // Grant the permission if needed.
387                 if (!permission.isGranted()) {
388                     permission.setGranted(true);
389                     mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
390                             permission.getName(), mUserHandle);
391                 }
392 
393                 // Update the permission flags.
394                 if (!fixedByTheUser) {
395                     // Now the apps can ask for the permission as the user
396                     // no longer has it fixed in a denied state.
397                     if (permission.isUserFixed() || permission.isUserSet()) {
398                         permission.setUserFixed(false);
399                         permission.setUserSet(false);
400                         mPackageManager.updatePermissionFlags(permission.getName(),
401                                 mPackageInfo.packageName,
402                                 PackageManager.FLAG_PERMISSION_USER_FIXED
403                                         | PackageManager.FLAG_PERMISSION_USER_SET,
404                                 0, mUserHandle);
405                     }
406                 }
407             } else {
408                 // Legacy apps cannot have a not granted permission but just in case.
409                 if (!permission.isGranted()) {
410                     continue;
411                 }
412 
413                 int killUid = -1;
414                 int mask = 0;
415 
416                 // If the permissions has no corresponding app op, then it is a
417                 // third-party one and we do not offer toggling of such permissions.
418                 if (permission.hasAppOp()) {
419                     if (!permission.isAppOpAllowed()) {
420                         permission.setAppOpAllowed(true);
421                         // Enable the app op.
422                         mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
423 
424                         // Legacy apps do not know that they have to retry access to a
425                         // resource due to changes in runtime permissions (app ops in this
426                         // case). Therefore, we restart them on app op change, so they
427                         // can pick up the change.
428                         killUid = uid;
429                     }
430 
431                     // Mark that the permission should not be be granted on upgrade
432                     // when the app begins supporting runtime permissions.
433                     if (permission.shouldRevokeOnUpgrade()) {
434                         permission.setRevokeOnUpgrade(false);
435                         mask |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
436                     }
437                 }
438 
439                 // Granting a permission explicitly means the user already
440                 // reviewed it so clear the review flag on every grant.
441                 if (permission.isReviewRequired()) {
442                     permission.resetReviewRequired();
443                     mask |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
444                 }
445 
446                 if (mask != 0) {
447                     mPackageManager.updatePermissionFlags(permission.getName(),
448                             mPackageInfo.packageName, mask, 0, mUserHandle);
449                 }
450 
451                 if (killUid != -1) {
452                     mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
453                 }
454             }
455         }
456 
457         return true;
458     }
459 
revokeRuntimePermissions(boolean fixedByTheUser)460     public boolean revokeRuntimePermissions(boolean fixedByTheUser) {
461         return revokeRuntimePermissions(fixedByTheUser, null);
462     }
463 
revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions)464     public boolean revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
465         final int uid = mPackageInfo.applicationInfo.uid;
466 
467         // We toggle permissions only to apps that support runtime
468         // permissions, otherwise we toggle the app op corresponding
469         // to the permission if the permission is granted to the app.
470         for (Permission permission : mPermissions.values()) {
471             if (filterPermissions != null
472                     && !ArrayUtils.contains(filterPermissions, permission.getName())) {
473                 continue;
474             }
475 
476             if (mAppSupportsRuntimePermissions) {
477                 // Do not touch permissions fixed by the system.
478                 if (permission.isSystemFixed()) {
479                     return false;
480                 }
481 
482                 // Revoke the permission if needed.
483                 if (permission.isGranted()) {
484                     permission.setGranted(false);
485                     mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
486                             permission.getName(), mUserHandle);
487                 }
488 
489                 // Update the permission flags.
490                 if (fixedByTheUser) {
491                     // Take a note that the user fixed the permission.
492                     if (permission.isUserSet() || !permission.isUserFixed()) {
493                         permission.setUserSet(false);
494                         permission.setUserFixed(true);
495                         mPackageManager.updatePermissionFlags(permission.getName(),
496                                 mPackageInfo.packageName,
497                                 PackageManager.FLAG_PERMISSION_USER_SET
498                                         | PackageManager.FLAG_PERMISSION_USER_FIXED,
499                                 PackageManager.FLAG_PERMISSION_USER_FIXED,
500                                 mUserHandle);
501                     }
502                 } else {
503                     if (!permission.isUserSet() || permission.isUserFixed()) {
504                         permission.setUserSet(true);
505                         permission.setUserFixed(false);
506                         // Take a note that the user already chose once.
507                         mPackageManager.updatePermissionFlags(permission.getName(),
508                                 mPackageInfo.packageName,
509                                 PackageManager.FLAG_PERMISSION_USER_SET
510                                         | PackageManager.FLAG_PERMISSION_USER_FIXED,
511                                 PackageManager.FLAG_PERMISSION_USER_SET,
512                                 mUserHandle);
513                     }
514                 }
515             } else {
516                 // Legacy apps cannot have a non-granted permission but just in case.
517                 if (!permission.isGranted()) {
518                     continue;
519                 }
520 
521                 int mask = 0;
522                 int flags = 0;
523                 int killUid = -1;
524 
525                 // If the permission has no corresponding app op, then it is a
526                 // third-party one and we do not offer toggling of such permissions.
527                 if (permission.hasAppOp()) {
528                     if (permission.isAppOpAllowed()) {
529                         permission.setAppOpAllowed(false);
530                         // Disable the app op.
531                         mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_IGNORED);
532 
533                         // Disabling an app op may put the app in a situation in which it
534                         // has a handle to state it shouldn't have, so we have to kill the
535                         // app. This matches the revoke runtime permission behavior.
536                         killUid = uid;
537                     }
538 
539                     // Mark that the permission should not be granted on upgrade
540                     // when the app begins supporting runtime permissions.
541                     if (!permission.shouldRevokeOnUpgrade()) {
542                         permission.setRevokeOnUpgrade(true);
543                         mask |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
544                         flags |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
545                     }
546                 }
547 
548                 if (mask != 0) {
549                     mPackageManager.updatePermissionFlags(permission.getName(),
550                             mPackageInfo.packageName, mask, flags, mUserHandle);
551                 }
552 
553                 if (killUid != -1) {
554                     mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
555                 }
556             }
557         }
558 
559         return true;
560     }
561 
setPolicyFixed()562     public void setPolicyFixed() {
563         final int permissionCount = mPermissions.size();
564         for (int i = 0; i < permissionCount; i++) {
565             Permission permission = mPermissions.valueAt(i);
566             permission.setPolicyFixed(true);
567             mPackageManager.updatePermissionFlags(permission.getName(),
568                     mPackageInfo.packageName,
569                     PackageManager.FLAG_PERMISSION_POLICY_FIXED,
570                     PackageManager.FLAG_PERMISSION_POLICY_FIXED,
571                     mUserHandle);
572         }
573     }
574 
getPermissions()575     public List<Permission> getPermissions() {
576         return new ArrayList<>(mPermissions.values());
577     }
578 
getFlags()579     public int getFlags() {
580         int flags = 0;
581         final int permissionCount = mPermissions.size();
582         for (int i = 0; i < permissionCount; i++) {
583             Permission permission = mPermissions.valueAt(i);
584             flags |= permission.getFlags();
585         }
586         return flags;
587     }
588 
isUserFixed()589     public boolean isUserFixed() {
590         final int permissionCount = mPermissions.size();
591         for (int i = 0; i < permissionCount; i++) {
592             Permission permission = mPermissions.valueAt(i);
593             if (permission.isUserFixed()) {
594                 return true;
595             }
596         }
597         return false;
598     }
599 
isPolicyFixed()600     public boolean isPolicyFixed() {
601         final int permissionCount = mPermissions.size();
602         for (int i = 0; i < permissionCount; i++) {
603             Permission permission = mPermissions.valueAt(i);
604             if (permission.isPolicyFixed()) {
605                 return true;
606             }
607         }
608         return false;
609     }
610 
isUserSet()611     public boolean isUserSet() {
612         final int permissionCount = mPermissions.size();
613         for (int i = 0; i < permissionCount; i++) {
614             Permission permission = mPermissions.valueAt(i);
615             if (permission.isUserSet()) {
616                 return true;
617             }
618         }
619         return false;
620     }
621 
isSystemFixed()622     public boolean isSystemFixed() {
623         final int permissionCount = mPermissions.size();
624         for (int i = 0; i < permissionCount; i++) {
625             Permission permission = mPermissions.valueAt(i);
626             if (permission.isSystemFixed()) {
627                 return true;
628             }
629         }
630         return false;
631     }
632 
633     @Override
compareTo(AppPermissionGroup another)634     public int compareTo(AppPermissionGroup another) {
635         final int result = mCollator.compare(mLabel.toString(), another.mLabel.toString());
636         if (result == 0) {
637             // Unbadged before badged.
638             return mPackageInfo.applicationInfo.uid
639                     - another.mPackageInfo.applicationInfo.uid;
640         }
641         return result;
642     }
643 
644     @Override
equals(Object obj)645     public boolean equals(Object obj) {
646         if (this == obj) {
647             return true;
648         }
649 
650         if (obj == null) {
651             return false;
652         }
653 
654         if (getClass() != obj.getClass()) {
655             return false;
656         }
657 
658         AppPermissionGroup other = (AppPermissionGroup) obj;
659 
660         if (mName == null) {
661             if (other.mName != null) {
662                 return false;
663             }
664         } else if (!mName.equals(other.mName)) {
665             return false;
666         }
667 
668         return true;
669     }
670 
671     @Override
hashCode()672     public int hashCode() {
673         return mName != null ? mName.hashCode() : 0;
674     }
675 
676     @Override
toString()677     public String toString() {
678         StringBuilder builder = new StringBuilder();
679         builder.append(getClass().getSimpleName());
680         builder.append("{name=").append(mName);
681         if (!mPermissions.isEmpty()) {
682             builder.append(", <has permissions>}");
683         } else {
684             builder.append('}');
685         }
686         return builder.toString();
687     }
688 
addPermission(Permission permission)689     private void addPermission(Permission permission) {
690         mPermissions.put(permission.getName(), permission);
691         if (permission.isEphemeral()) {
692             mContainsEphemeralPermission = true;
693         }
694         if (!permission.isRuntimeOnly()) {
695             mContainsPreRuntimePermission = true;
696         }
697     }
698 }
699