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 android.content.pm.parsing.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 isDocumenter()307 public boolean isDocumenter() { 308 return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_DOCUMENTER) != 0; 309 } 310 isConfigurator()311 public boolean isConfigurator() { 312 return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_CONFIGURATOR) != 0; 313 } 314 isIncidentReportApprover()315 public boolean isIncidentReportApprover() { 316 return (mPermissionInfo.protectionLevel 317 & PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER) != 0; 318 } 319 isAppPredictor()320 public boolean isAppPredictor() { 321 return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR) 322 != 0; 323 } 324 isCompanion()325 public boolean isCompanion() { 326 return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_COMPANION) != 0; 327 } 328 isRetailDemo()329 public boolean isRetailDemo() { 330 return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO) != 0; 331 } 332 isRecents()333 public boolean isRecents() { 334 return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_RECENTS) != 0; 335 } 336 isRole()337 public boolean isRole() { 338 return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_ROLE) != 0; 339 } 340 isKnownSigner()341 public boolean isKnownSigner() { 342 return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0; 343 } 344 getKnownCerts()345 public Set<String> getKnownCerts() { 346 return mPermissionInfo.knownCerts; 347 } 348 transfer(@onNull String oldPackageName, @NonNull String newPackageName)349 public void transfer(@NonNull String oldPackageName, @NonNull String newPackageName) { 350 if (!oldPackageName.equals(mPermissionInfo.packageName)) { 351 return; 352 } 353 final PermissionInfo newPermissionInfo = new PermissionInfo(); 354 newPermissionInfo.name = mPermissionInfo.name; 355 newPermissionInfo.packageName = newPackageName; 356 newPermissionInfo.protectionLevel = mPermissionInfo.protectionLevel; 357 mPermissionInfo = newPermissionInfo; 358 mReconciled = false; 359 mUid = 0; 360 mGids = EmptyArray.INT; 361 mGidsPerUser = false; 362 } 363 addToTree(@rotectionLevel int protectionLevel, @NonNull PermissionInfo permissionInfo, @NonNull Permission permissionTree)364 public boolean addToTree(@ProtectionLevel int protectionLevel, 365 @NonNull PermissionInfo permissionInfo, @NonNull Permission permissionTree) { 366 final boolean changed = 367 (mPermissionInfo.protectionLevel != protectionLevel 368 || !mReconciled 369 || mUid != permissionTree.mUid 370 || !Objects.equals(mPermissionInfo.packageName, 371 permissionTree.mPermissionInfo.packageName) 372 || !comparePermissionInfos(mPermissionInfo, permissionInfo)); 373 mPermissionInfo = new PermissionInfo(permissionInfo); 374 mPermissionInfo.packageName = permissionTree.mPermissionInfo.packageName; 375 mPermissionInfo.protectionLevel = protectionLevel; 376 mReconciled = true; 377 mUid = permissionTree.mUid; 378 return changed; 379 } 380 updateDynamicPermission(@onNull Collection<Permission> permissionTrees)381 public void updateDynamicPermission(@NonNull Collection<Permission> permissionTrees) { 382 if (PackageManagerService.DEBUG_SETTINGS) { 383 Log.v(TAG, "Dynamic permission: name=" + getName() + " pkg=" + getPackageName() 384 + " info=" + mPermissionInfo); 385 } 386 if (mType == TYPE_DYNAMIC) { 387 final Permission tree = findPermissionTree(permissionTrees, mPermissionInfo.name); 388 if (tree != null) { 389 mPermissionInfo.packageName = tree.mPermissionInfo.packageName; 390 mReconciled = true; 391 mUid = tree.mUid; 392 } 393 } 394 } 395 isOverridingSystemPermission(@ullable Permission permission, @NonNull PermissionInfo permissionInfo, @NonNull PackageManagerInternal packageManagerInternal)396 public static boolean isOverridingSystemPermission(@Nullable Permission permission, 397 @NonNull PermissionInfo permissionInfo, 398 @NonNull PackageManagerInternal packageManagerInternal) { 399 if (permission == null || Objects.equals(permission.mPermissionInfo.packageName, 400 permissionInfo.packageName)) { 401 return false; 402 } 403 if (!permission.mReconciled) { 404 return false; 405 } 406 final AndroidPackage currentPackage = packageManagerInternal.getPackage( 407 permission.mPermissionInfo.packageName); 408 if (currentPackage == null) { 409 return false; 410 } 411 return currentPackage.isSystem(); 412 } 413 414 @NonNull createOrUpdate(@ullable Permission permission, @NonNull PermissionInfo permissionInfo, @NonNull AndroidPackage pkg, @NonNull Collection<Permission> permissionTrees, boolean isOverridingSystemPermission)415 public static Permission createOrUpdate(@Nullable Permission permission, 416 @NonNull PermissionInfo permissionInfo, @NonNull AndroidPackage pkg, 417 @NonNull Collection<Permission> permissionTrees, boolean isOverridingSystemPermission) { 418 // Allow system apps to redefine non-system permissions 419 boolean ownerChanged = false; 420 if (permission != null && !Objects.equals(permission.mPermissionInfo.packageName, 421 permissionInfo.packageName)) { 422 if (pkg.isSystem()) { 423 if (permission.mType == Permission.TYPE_CONFIG && !permission.mReconciled) { 424 // It's a built-in permission and no owner, take ownership now 425 permissionInfo.flags |= PermissionInfo.FLAG_INSTALLED; 426 permission.mPermissionInfo = permissionInfo; 427 permission.mReconciled = true; 428 permission.mUid = pkg.getUid(); 429 } else if (!isOverridingSystemPermission) { 430 Slog.w(TAG, "New decl " + pkg + " of permission " 431 + permissionInfo.name + " is system; overriding " 432 + permission.mPermissionInfo.packageName); 433 ownerChanged = true; 434 permission = null; 435 } 436 } 437 } 438 boolean wasNonRuntime = permission != null && permission.mType != TYPE_CONFIG 439 && !permission.isRuntime(); 440 if (permission == null) { 441 permission = new Permission(permissionInfo.name, permissionInfo.packageName, 442 TYPE_MANIFEST); 443 } 444 StringBuilder r = null; 445 if (!permission.mReconciled) { 446 if (permission.mPermissionInfo.packageName == null 447 || permission.mPermissionInfo.packageName.equals(permissionInfo.packageName)) { 448 final Permission tree = findPermissionTree(permissionTrees, permissionInfo.name); 449 if (tree == null 450 || tree.mPermissionInfo.packageName.equals(permissionInfo.packageName)) { 451 permissionInfo.flags |= PermissionInfo.FLAG_INSTALLED; 452 permission.mPermissionInfo = permissionInfo; 453 permission.mReconciled = true; 454 permission.mUid = pkg.getUid(); 455 if (PackageManagerService.DEBUG_PACKAGE_SCANNING) { 456 if (r == null) { 457 r = new StringBuilder(256); 458 } else { 459 r.append(' '); 460 } 461 r.append(permissionInfo.name); 462 } 463 } else { 464 Slog.w(TAG, "Permission " + permissionInfo.name + " from package " 465 + permissionInfo.packageName + " ignored: base tree " 466 + tree.mPermissionInfo.name + " is from package " 467 + tree.mPermissionInfo.packageName); 468 } 469 } else { 470 Slog.w(TAG, "Permission " + permissionInfo.name + " from package " 471 + permissionInfo.packageName + " ignored: original from " 472 + permission.mPermissionInfo.packageName); 473 } 474 } else if (PackageManagerService.DEBUG_PACKAGE_SCANNING) { 475 if (r == null) { 476 r = new StringBuilder(256); 477 } else { 478 r.append(' '); 479 } 480 r.append("DUP:"); 481 r.append(permissionInfo.name); 482 } 483 if (permission.isRuntime() && (ownerChanged || wasNonRuntime)) { 484 // If this is a runtime permission and the owner has changed, or this wasn't a runtime 485 // permission, then permission state should be cleaned up 486 permission.mDefinitionChanged = true; 487 } 488 if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) { 489 Log.d(TAG, " Permissions: " + r); 490 } 491 return permission; 492 } 493 494 @NonNull enforcePermissionTree(@onNull Collection<Permission> permissionTrees, @NonNull String permissionName, int callingUid)495 public static Permission enforcePermissionTree(@NonNull Collection<Permission> permissionTrees, 496 @NonNull String permissionName, int callingUid) { 497 if (permissionName != null) { 498 final Permission permissionTree = Permission.findPermissionTree(permissionTrees, 499 permissionName); 500 if (permissionTree != null) { 501 if (permissionTree.getUid() == UserHandle.getAppId(callingUid)) { 502 return permissionTree; 503 } 504 throw new SecurityException("Calling uid " + callingUid 505 + " is not allowed to add to permission tree " 506 + permissionTree.getName() + " owned by uid " 507 + permissionTree.getUid()); 508 } 509 } 510 throw new SecurityException("No permission tree found for " + permissionName); 511 } 512 513 @Nullable findPermissionTree(@onNull Collection<Permission> permissionTrees, @NonNull String permissionName)514 private static Permission findPermissionTree(@NonNull Collection<Permission> permissionTrees, 515 @NonNull String permissionName) { 516 for (final Permission permissionTree : permissionTrees) { 517 final String permissionTreeName = permissionTree.getName(); 518 if (permissionName.startsWith(permissionTreeName) 519 && permissionName.length() > permissionTreeName.length() 520 && permissionName.charAt(permissionTreeName.length()) == '.') { 521 return permissionTree; 522 } 523 } 524 return null; 525 } 526 527 @Nullable getBackgroundPermission()528 public String getBackgroundPermission() { 529 return mPermissionInfo.backgroundPermission; 530 } 531 532 @Nullable getGroup()533 public String getGroup() { 534 return mPermissionInfo.group; 535 } 536 getProtection()537 public int getProtection() { 538 return mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE; 539 } 540 getProtectionFlags()541 public int getProtectionFlags() { 542 return mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_FLAGS; 543 } 544 545 @NonNull generatePermissionInfo(int flags)546 public PermissionInfo generatePermissionInfo(int flags) { 547 return generatePermissionInfo(flags, Build.VERSION_CODES.CUR_DEVELOPMENT); 548 } 549 550 @NonNull generatePermissionInfo(int flags, int targetSdkVersion)551 public PermissionInfo generatePermissionInfo(int flags, int targetSdkVersion) { 552 final PermissionInfo permissionInfo; 553 if (mPermissionInfo != null) { 554 permissionInfo = new PermissionInfo(mPermissionInfo); 555 if ((flags & PackageManager.GET_META_DATA) != PackageManager.GET_META_DATA) { 556 permissionInfo.metaData = null; 557 } 558 } else { 559 permissionInfo = new PermissionInfo(); 560 permissionInfo.name = mPermissionInfo.name; 561 permissionInfo.packageName = mPermissionInfo.packageName; 562 permissionInfo.nonLocalizedLabel = mPermissionInfo.name; 563 } 564 if (targetSdkVersion >= Build.VERSION_CODES.O) { 565 permissionInfo.protectionLevel = mPermissionInfo.protectionLevel; 566 } else { 567 final int protection = mPermissionInfo.protectionLevel 568 & PermissionInfo.PROTECTION_MASK_BASE; 569 if (protection == PermissionInfo.PROTECTION_SIGNATURE) { 570 // Signature permission's protection flags are always reported. 571 permissionInfo.protectionLevel = mPermissionInfo.protectionLevel; 572 } else { 573 permissionInfo.protectionLevel = protection; 574 } 575 } 576 return permissionInfo; 577 } 578 comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2)579 private static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) { 580 if (pi1.icon != pi2.icon) return false; 581 if (pi1.logo != pi2.logo) return false; 582 if (pi1.protectionLevel != pi2.protectionLevel) return false; 583 if (!Objects.equals(pi1.name, pi2.name)) return false; 584 if (!Objects.equals(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false; 585 // We'll take care of setting this one. 586 if (!Objects.equals(pi1.packageName, pi2.packageName)) return false; 587 // These are not currently stored in settings. 588 //if (!compareStrings(pi1.group, pi2.group)) return false; 589 //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false; 590 //if (pi1.labelRes != pi2.labelRes) return false; 591 //if (pi1.descriptionRes != pi2.descriptionRes) return false; 592 return true; 593 } 594 } 595