• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 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.server.pm.permission;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.UserIdInt;
23 import android.content.pm.PackageManager;
24 import android.content.pm.PackageManagerInternal;
25 import android.content.pm.PermissionInfo;
26 import com.android.server.pm.pkg.component.ParsedPermission;
27 import android.os.Build;
28 import android.os.UserHandle;
29 import android.util.Log;
30 import android.util.Slog;
31 
32 import com.android.server.pm.PackageManagerService;
33 import com.android.server.pm.parsing.pkg.AndroidPackage;
34 
35 import libcore.util.EmptyArray;
36 
37 import java.lang.annotation.Retention;
38 import java.lang.annotation.RetentionPolicy;
39 import java.util.Collection;
40 import java.util.Objects;
41 import java.util.Set;
42 
43 /**
44  * Permission definition.
45  */
46 public final class Permission {
47     private static final String TAG = "Permission";
48 
49     public static final int TYPE_MANIFEST = LegacyPermission.TYPE_MANIFEST;
50     public static final int TYPE_CONFIG = LegacyPermission.TYPE_CONFIG;
51     public static final int TYPE_DYNAMIC = LegacyPermission.TYPE_DYNAMIC;
52     @IntDef({
53             TYPE_MANIFEST,
54             TYPE_CONFIG,
55             TYPE_DYNAMIC,
56     })
57     @Retention(RetentionPolicy.SOURCE)
58     public @interface PermissionType {}
59 
60     @IntDef({
61             PermissionInfo.PROTECTION_DANGEROUS,
62             PermissionInfo.PROTECTION_NORMAL,
63             PermissionInfo.PROTECTION_SIGNATURE,
64             PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM,
65             PermissionInfo.PROTECTION_INTERNAL,
66     })
67     @Retention(RetentionPolicy.SOURCE)
68     public @interface ProtectionLevel {}
69 
70     @NonNull
71     private PermissionInfo mPermissionInfo;
72 
73     private boolean mReconciled;
74 
75     @PermissionType
76     private final int mType;
77 
78     /** UID that owns the definition of this permission */
79     private int mUid;
80 
81     /** Additional GIDs given to apps granted this permission */
82     @NonNull
83     private int[] mGids = EmptyArray.INT;
84 
85     /**
86      * Flag indicating that {@link #mGids} should be adjusted based on the
87      * {@link UserHandle} the granted app is running as.
88      */
89     private boolean mGidsPerUser;
90 
91     private boolean mDefinitionChanged;
92 
Permission(@onNull String name, @NonNull String packageName, @PermissionType int type)93     public Permission(@NonNull String name, @NonNull String packageName,
94             @PermissionType int type) {
95         mPermissionInfo = new PermissionInfo();
96         mPermissionInfo.name = name;
97         mPermissionInfo.packageName = packageName;
98         // Default to most conservative protection level.
99         mPermissionInfo.protectionLevel = PermissionInfo.PROTECTION_SIGNATURE;
100         mType = type;
101     }
102 
Permission(@onNull PermissionInfo permissionInfo, @PermissionType int type)103     public Permission(@NonNull PermissionInfo permissionInfo, @PermissionType int type) {
104         mPermissionInfo = permissionInfo;
105         mType = type;
106     }
107 
108     @NonNull
getPermissionInfo()109     public PermissionInfo getPermissionInfo() {
110         return mPermissionInfo;
111     }
112 
setPermissionInfo(@ullable PermissionInfo permissionInfo)113     public void setPermissionInfo(@Nullable PermissionInfo permissionInfo) {
114         if (permissionInfo != null) {
115             mPermissionInfo = permissionInfo;
116         } else {
117             final PermissionInfo newPermissionInfo = new PermissionInfo();
118             newPermissionInfo.name = mPermissionInfo.name;
119             newPermissionInfo.packageName = mPermissionInfo.packageName;
120             newPermissionInfo.protectionLevel = mPermissionInfo.protectionLevel;
121             mPermissionInfo = newPermissionInfo;
122         }
123         mReconciled = permissionInfo != null;
124     }
125 
126     @NonNull
getName()127     public String getName() {
128         return mPermissionInfo.name;
129     }
130 
getProtectionLevel()131     public int getProtectionLevel() {
132         return mPermissionInfo.protectionLevel;
133     }
134 
135     @NonNull
getPackageName()136     public String getPackageName() {
137         return mPermissionInfo.packageName;
138     }
139 
getType()140     public int getType() {
141         return mType;
142     }
143 
getUid()144     public int getUid() {
145         return mUid;
146     }
147 
hasGids()148     public boolean hasGids() {
149         return mGids.length != 0;
150     }
151 
152     @NonNull
getRawGids()153     public int[] getRawGids() {
154         return mGids;
155     }
156 
areGidsPerUser()157     public boolean areGidsPerUser() {
158         return mGidsPerUser;
159     }
160 
setGids(@onNull int[] gids, boolean gidsPerUser)161     public void setGids(@NonNull int[] gids, boolean gidsPerUser) {
162         mGids = gids;
163         mGidsPerUser = gidsPerUser;
164     }
165 
166     @NonNull
computeGids(@serIdInt int userId)167     public int[] computeGids(@UserIdInt int userId) {
168         if (mGidsPerUser) {
169             final int[] userGids = new int[mGids.length];
170             for (int i = 0; i < mGids.length; i++) {
171                 final int gid = mGids[i];
172                 userGids[i] = UserHandle.getUid(userId, gid);
173             }
174             return userGids;
175         } else {
176             return mGids.length != 0 ? mGids.clone() : mGids;
177         }
178     }
179 
isDefinitionChanged()180     public boolean isDefinitionChanged() {
181         return mDefinitionChanged;
182     }
183 
setDefinitionChanged(boolean definitionChanged)184     public void setDefinitionChanged(boolean definitionChanged) {
185         mDefinitionChanged = definitionChanged;
186     }
187 
calculateFootprint(@onNull Permission permission)188     public int calculateFootprint(@NonNull Permission permission) {
189         if (mUid == permission.mUid) {
190             return permission.mPermissionInfo.name.length()
191                     + permission.mPermissionInfo.calculateFootprint();
192         }
193         return 0;
194     }
195 
isPermission(@onNull ParsedPermission parsedPermission)196     public boolean isPermission(@NonNull ParsedPermission parsedPermission) {
197         if (mPermissionInfo == null) {
198             return false;
199         }
200         return Objects.equals(mPermissionInfo.packageName, parsedPermission.getPackageName())
201                 && Objects.equals(mPermissionInfo.name, parsedPermission.getName());
202     }
203 
isDynamic()204     public boolean isDynamic() {
205         return mType == TYPE_DYNAMIC;
206     }
207 
isNormal()208     public boolean isNormal() {
209         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
210                 == PermissionInfo.PROTECTION_NORMAL;
211     }
isRuntime()212     public boolean isRuntime() {
213         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
214                 == PermissionInfo.PROTECTION_DANGEROUS;
215     }
216 
isInstalled()217     public boolean isInstalled() {
218         return (mPermissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0;
219     }
220 
isRemoved()221     public boolean isRemoved() {
222         return (mPermissionInfo.flags & PermissionInfo.FLAG_REMOVED) != 0;
223     }
224 
isSoftRestricted()225     public boolean isSoftRestricted() {
226         return (mPermissionInfo.flags & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0;
227     }
228 
isHardRestricted()229     public boolean isHardRestricted() {
230         return (mPermissionInfo.flags & PermissionInfo.FLAG_HARD_RESTRICTED) != 0;
231     }
232 
isHardOrSoftRestricted()233     public boolean isHardOrSoftRestricted() {
234         return (mPermissionInfo.flags & (PermissionInfo.FLAG_HARD_RESTRICTED
235                 | PermissionInfo.FLAG_SOFT_RESTRICTED)) != 0;
236     }
237 
isImmutablyRestricted()238     public boolean isImmutablyRestricted() {
239         return (mPermissionInfo.flags & PermissionInfo.FLAG_IMMUTABLY_RESTRICTED) != 0;
240     }
241 
isSignature()242     public boolean isSignature() {
243         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
244                 == PermissionInfo.PROTECTION_SIGNATURE;
245     }
246 
isInternal()247     public boolean isInternal() {
248         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
249                 == PermissionInfo.PROTECTION_INTERNAL;
250     }
251 
isAppOp()252     public boolean isAppOp() {
253         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
254     }
255 
isDevelopment()256     public boolean isDevelopment() {
257         return isSignature() && (mPermissionInfo.protectionLevel
258                 & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0;
259     }
260 
isInstaller()261     public boolean isInstaller() {
262         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0;
263     }
264 
isInstant()265     public boolean isInstant() {
266         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0;
267     }
268 
isOem()269     public boolean isOem() {
270         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_OEM) != 0;
271     }
272 
isPre23()273     public boolean isPre23() {
274         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_PRE23) != 0;
275     }
276 
isPreInstalled()277     public boolean isPreInstalled() {
278         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0;
279     }
280 
isPrivileged()281     public boolean isPrivileged() {
282         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0;
283     }
284 
isRuntimeOnly()285     public boolean isRuntimeOnly() {
286         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0;
287     }
288 
isSetup()289     public boolean isSetup() {
290         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_SETUP) != 0;
291     }
292 
isVerifier()293     public boolean isVerifier() {
294         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0;
295     }
296 
isVendorPrivileged()297     public boolean isVendorPrivileged() {
298         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED)
299                 != 0;
300     }
301 
isSystemTextClassifier()302     public boolean isSystemTextClassifier() {
303         return (mPermissionInfo.protectionLevel
304                 & PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER) != 0;
305     }
306 
isConfigurator()307     public boolean isConfigurator() {
308         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_CONFIGURATOR) != 0;
309     }
310 
isIncidentReportApprover()311     public boolean isIncidentReportApprover() {
312         return (mPermissionInfo.protectionLevel
313                 & PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER) != 0;
314     }
315 
isAppPredictor()316     public boolean isAppPredictor() {
317         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR)
318                 != 0;
319     }
320 
isCompanion()321     public boolean isCompanion() {
322         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_COMPANION) != 0;
323     }
324 
isRetailDemo()325     public boolean isRetailDemo() {
326         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO) != 0;
327     }
328 
isRecents()329     public boolean isRecents() {
330         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_RECENTS) != 0;
331     }
332 
isRole()333     public boolean isRole() {
334         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_ROLE) != 0;
335     }
336 
isKnownSigner()337     public boolean isKnownSigner() {
338         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0;
339     }
340 
getKnownCerts()341     public Set<String> getKnownCerts() {
342         return mPermissionInfo.knownCerts;
343     }
344 
transfer(@onNull String oldPackageName, @NonNull String newPackageName)345     public void transfer(@NonNull String oldPackageName, @NonNull String newPackageName) {
346         if (!oldPackageName.equals(mPermissionInfo.packageName)) {
347             return;
348         }
349         final PermissionInfo newPermissionInfo = new PermissionInfo();
350         newPermissionInfo.name = mPermissionInfo.name;
351         newPermissionInfo.packageName = newPackageName;
352         newPermissionInfo.protectionLevel = mPermissionInfo.protectionLevel;
353         mPermissionInfo = newPermissionInfo;
354         mReconciled = false;
355         mUid = 0;
356         mGids = EmptyArray.INT;
357         mGidsPerUser = false;
358     }
359 
addToTree(@rotectionLevel int protectionLevel, @NonNull PermissionInfo permissionInfo, @NonNull Permission permissionTree)360     public boolean addToTree(@ProtectionLevel int protectionLevel,
361             @NonNull PermissionInfo permissionInfo, @NonNull Permission permissionTree) {
362         final boolean changed =
363                 (mPermissionInfo.protectionLevel != protectionLevel
364                     || !mReconciled
365                     || mUid != permissionTree.mUid
366                     || !Objects.equals(mPermissionInfo.packageName,
367                             permissionTree.mPermissionInfo.packageName)
368                     || !comparePermissionInfos(mPermissionInfo, permissionInfo));
369         mPermissionInfo = new PermissionInfo(permissionInfo);
370         mPermissionInfo.packageName = permissionTree.mPermissionInfo.packageName;
371         mPermissionInfo.protectionLevel = protectionLevel;
372         mReconciled = true;
373         mUid = permissionTree.mUid;
374         return changed;
375     }
376 
updateDynamicPermission(@onNull Collection<Permission> permissionTrees)377     public void updateDynamicPermission(@NonNull Collection<Permission> permissionTrees) {
378         if (PackageManagerService.DEBUG_SETTINGS) {
379             Log.v(TAG, "Dynamic permission: name=" + getName() + " pkg=" + getPackageName()
380                     + " info=" + mPermissionInfo);
381         }
382         if (mType == TYPE_DYNAMIC) {
383             final Permission tree = findPermissionTree(permissionTrees, mPermissionInfo.name);
384             if (tree != null) {
385                 mPermissionInfo.packageName = tree.mPermissionInfo.packageName;
386                 mReconciled = true;
387                 mUid = tree.mUid;
388             }
389         }
390     }
391 
isOverridingSystemPermission(@ullable Permission permission, @NonNull PermissionInfo permissionInfo, @NonNull PackageManagerInternal packageManagerInternal)392     public static boolean isOverridingSystemPermission(@Nullable Permission permission,
393             @NonNull PermissionInfo permissionInfo,
394             @NonNull PackageManagerInternal packageManagerInternal) {
395         if (permission == null || Objects.equals(permission.mPermissionInfo.packageName,
396                 permissionInfo.packageName)) {
397             return false;
398         }
399         if (!permission.mReconciled) {
400             return false;
401         }
402         final AndroidPackage currentPackage = packageManagerInternal.getPackage(
403                 permission.mPermissionInfo.packageName);
404         if (currentPackage == null) {
405             return false;
406         }
407         return currentPackage.isSystem();
408     }
409 
410     @NonNull
createOrUpdate(@ullable Permission permission, @NonNull PermissionInfo permissionInfo, @NonNull AndroidPackage pkg, @NonNull Collection<Permission> permissionTrees, boolean isOverridingSystemPermission)411     public static Permission createOrUpdate(@Nullable Permission permission,
412             @NonNull PermissionInfo permissionInfo, @NonNull AndroidPackage pkg,
413             @NonNull Collection<Permission> permissionTrees, boolean isOverridingSystemPermission) {
414         // Allow system apps to redefine non-system permissions
415         boolean ownerChanged = false;
416         if (permission != null && !Objects.equals(permission.mPermissionInfo.packageName,
417                 permissionInfo.packageName)) {
418             if (pkg.isSystem()) {
419                 if (permission.mType == Permission.TYPE_CONFIG && !permission.mReconciled) {
420                     // It's a built-in permission and no owner, take ownership now
421                     permissionInfo.flags |= PermissionInfo.FLAG_INSTALLED;
422                     permission.mPermissionInfo = permissionInfo;
423                     permission.mReconciled = true;
424                     permission.mUid = pkg.getUid();
425                 } else if (!isOverridingSystemPermission) {
426                     Slog.w(TAG, "New decl " + pkg + " of permission  "
427                             + permissionInfo.name + " is system; overriding "
428                             + permission.mPermissionInfo.packageName);
429                     ownerChanged = true;
430                     permission = null;
431                 }
432             }
433         }
434         boolean wasNonInternal = permission != null && permission.mType != TYPE_CONFIG
435                 && !permission.isInternal();
436         boolean wasNonRuntime = permission != null && permission.mType != TYPE_CONFIG
437                 && !permission.isRuntime();
438         if (permission == null) {
439             permission = new Permission(permissionInfo.name, permissionInfo.packageName,
440                     TYPE_MANIFEST);
441         }
442         StringBuilder r = null;
443         if (!permission.mReconciled) {
444             if (permission.mPermissionInfo.packageName == null
445                     || permission.mPermissionInfo.packageName.equals(permissionInfo.packageName)) {
446                 final Permission tree = findPermissionTree(permissionTrees, permissionInfo.name);
447                 if (tree == null
448                         || tree.mPermissionInfo.packageName.equals(permissionInfo.packageName)) {
449                     permissionInfo.flags |= PermissionInfo.FLAG_INSTALLED;
450                     permission.mPermissionInfo = permissionInfo;
451                     permission.mReconciled = true;
452                     permission.mUid = pkg.getUid();
453                     if (PackageManagerService.DEBUG_PACKAGE_SCANNING) {
454                         if (r == null) {
455                             r = new StringBuilder(256);
456                         } else {
457                             r.append(' ');
458                         }
459                         r.append(permissionInfo.name);
460                     }
461                 } else {
462                     Slog.w(TAG, "Permission " + permissionInfo.name + " from package "
463                             + permissionInfo.packageName + " ignored: base tree "
464                             + tree.mPermissionInfo.name + " is from package "
465                             + tree.mPermissionInfo.packageName);
466                 }
467             } else {
468                 Slog.w(TAG, "Permission " + permissionInfo.name + " from package "
469                         + permissionInfo.packageName + " ignored: original from "
470                         + permission.mPermissionInfo.packageName);
471             }
472         } else if (PackageManagerService.DEBUG_PACKAGE_SCANNING) {
473             if (r == null) {
474                 r = new StringBuilder(256);
475             } else {
476                 r.append(' ');
477             }
478             r.append("DUP:");
479             r.append(permissionInfo.name);
480         }
481         if ((permission.isInternal() && (ownerChanged || wasNonInternal))
482                 || (permission.isRuntime() && (ownerChanged || wasNonRuntime))) {
483             // If this is an internal/runtime permission and the owner has changed, or this wasn't a
484             // internal/runtime permission, then permission state should be cleaned up.
485             permission.mDefinitionChanged = true;
486         }
487         if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) {
488             Log.d(TAG, "  Permissions: " + r);
489         }
490         return permission;
491     }
492 
493     @NonNull
enforcePermissionTree(@onNull Collection<Permission> permissionTrees, @NonNull String permissionName, int callingUid)494     public static Permission enforcePermissionTree(@NonNull Collection<Permission> permissionTrees,
495             @NonNull String permissionName, int callingUid) {
496         if (permissionName != null) {
497             final Permission permissionTree = Permission.findPermissionTree(permissionTrees,
498                     permissionName);
499             if (permissionTree != null) {
500                 if (permissionTree.getUid() == UserHandle.getAppId(callingUid)) {
501                     return permissionTree;
502                 }
503             }
504         }
505         throw new SecurityException("Calling uid " + callingUid
506             + " is not allowed to add to or remove from the permission tree");
507     }
508 
509     @Nullable
findPermissionTree(@onNull Collection<Permission> permissionTrees, @NonNull String permissionName)510     private static Permission findPermissionTree(@NonNull Collection<Permission> permissionTrees,
511             @NonNull String permissionName) {
512         for (final Permission permissionTree : permissionTrees) {
513             final String permissionTreeName = permissionTree.getName();
514             if (permissionName.startsWith(permissionTreeName)
515                     && permissionName.length() > permissionTreeName.length()
516                     && permissionName.charAt(permissionTreeName.length()) == '.') {
517                 return permissionTree;
518             }
519         }
520         return null;
521     }
522 
523     @Nullable
getBackgroundPermission()524     public String getBackgroundPermission() {
525         return mPermissionInfo.backgroundPermission;
526     }
527 
528     @Nullable
getGroup()529     public String getGroup() {
530         return mPermissionInfo.group;
531     }
532 
getProtection()533     public int getProtection() {
534         return mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
535     }
536 
getProtectionFlags()537     public int getProtectionFlags() {
538         return mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_FLAGS;
539     }
540 
541     @NonNull
generatePermissionInfo(int flags)542     public PermissionInfo generatePermissionInfo(int flags) {
543         return generatePermissionInfo(flags, Build.VERSION_CODES.CUR_DEVELOPMENT);
544     }
545 
546     @NonNull
generatePermissionInfo(int flags, int targetSdkVersion)547     public PermissionInfo generatePermissionInfo(int flags, int targetSdkVersion) {
548         final PermissionInfo permissionInfo;
549         if (mPermissionInfo != null) {
550             permissionInfo = new PermissionInfo(mPermissionInfo);
551             if ((flags & PackageManager.GET_META_DATA) != PackageManager.GET_META_DATA) {
552                 permissionInfo.metaData = null;
553             }
554         } else {
555             permissionInfo = new PermissionInfo();
556             permissionInfo.name = mPermissionInfo.name;
557             permissionInfo.packageName = mPermissionInfo.packageName;
558             permissionInfo.nonLocalizedLabel = mPermissionInfo.name;
559         }
560         if (targetSdkVersion >= Build.VERSION_CODES.O) {
561             permissionInfo.protectionLevel = mPermissionInfo.protectionLevel;
562         } else {
563             final int protection = mPermissionInfo.protectionLevel
564                     & PermissionInfo.PROTECTION_MASK_BASE;
565             if (protection == PermissionInfo.PROTECTION_SIGNATURE) {
566                 // Signature permission's protection flags are always reported.
567                 permissionInfo.protectionLevel = mPermissionInfo.protectionLevel;
568             } else {
569                 permissionInfo.protectionLevel = protection;
570             }
571         }
572         return permissionInfo;
573     }
574 
comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2)575     private static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) {
576         if (pi1.icon != pi2.icon) return false;
577         if (pi1.logo != pi2.logo) return false;
578         if (pi1.protectionLevel != pi2.protectionLevel) return false;
579         if (!Objects.equals(pi1.name, pi2.name)) return false;
580         if (!Objects.equals(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false;
581         // We'll take care of setting this one.
582         if (!Objects.equals(pi1.packageName, pi2.packageName)) return false;
583         // These are not currently stored in settings.
584         //if (!compareStrings(pi1.group, pi2.group)) return false;
585         //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false;
586         //if (pi1.labelRes != pi2.labelRes) return false;
587         //if (pi1.descriptionRes != pi2.descriptionRes) return false;
588         return true;
589     }
590 }
591