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