1 /* 2 * Copyright (C) 2018 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.role.controller.model; 18 19 import android.app.ActivityManager; 20 import android.app.admin.DevicePolicyManager; 21 import android.app.ecm.EnhancedConfirmationManager; 22 import android.app.role.RoleManager; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.PackageManager; 28 import android.content.pm.SharedLibraryInfo; 29 import android.content.res.Resources; 30 import android.os.Build; 31 import android.os.UserHandle; 32 import android.os.UserManager; 33 import android.permission.flags.Flags; 34 import android.provider.Settings; 35 import android.text.TextUtils; 36 import android.util.ArrayMap; 37 import android.util.ArraySet; 38 import android.util.Log; 39 import android.util.SparseBooleanArray; 40 41 import androidx.annotation.IntDef; 42 import androidx.annotation.NonNull; 43 import androidx.annotation.Nullable; 44 import androidx.annotation.StringRes; 45 import androidx.annotation.VisibleForTesting; 46 47 import com.android.modules.utils.build.SdkLevel; 48 import com.android.role.controller.util.CollectionUtils; 49 import com.android.role.controller.util.IntentCompat; 50 import com.android.role.controller.util.PackageUtils; 51 import com.android.role.controller.util.RoleFlags; 52 import com.android.role.controller.util.RoleManagerCompat; 53 import com.android.role.controller.util.SignedPackageUtils; 54 import com.android.role.controller.util.UserUtils; 55 56 import java.lang.annotation.Retention; 57 import java.lang.annotation.RetentionPolicy; 58 import java.util.ArrayList; 59 import java.util.Collections; 60 import java.util.List; 61 import java.util.Objects; 62 import java.util.function.Supplier; 63 64 /** 65 * Specifies a role and its properties. 66 * <p> 67 * A role is a unique name within the system associated with certain privileges. There can be 68 * multiple applications qualifying for a role, but only a subset of them can become role holders. 69 * To qualify for a role, an application must meet certain requirements, including defining certain 70 * components in its manifest. Then the application will need user consent to become the role 71 * holder. 72 * <p> 73 * Upon becoming a role holder, the application may be granted certain permissions, have certain 74 * app ops set to certain modes and certain {@code Activity} components configured as preferred for 75 * certain {@code Intent} actions. When an application loses its role, these privileges will also be 76 * revoked. 77 * 78 * @see android.app.role.RoleManager 79 */ 80 public class Role { 81 82 private static final String LOG_TAG = Role.class.getSimpleName(); 83 84 private static final boolean DEBUG = false; 85 86 private static final String PACKAGE_NAME_ANDROID_SYSTEM = "android"; 87 88 @Retention(RetentionPolicy.SOURCE) 89 @IntDef({ 90 EXCLUSIVITY_NONE, 91 EXCLUSIVITY_USER, 92 EXCLUSIVITY_PROFILE_GROUP 93 }) 94 public @interface Exclusivity {} 95 96 /** 97 * Does not enforce any exclusivity, which means multiple apps may hold this role in a user. 98 */ 99 public static final int EXCLUSIVITY_NONE = 0; 100 101 /** Enforces exclusivity within one user. */ 102 public static final int EXCLUSIVITY_USER = 1; 103 104 /** 105 * Enforces exclusivity across all users (including profile users) in the same profile group. 106 */ 107 public static final int EXCLUSIVITY_PROFILE_GROUP = 2; 108 109 /** Set of valid exclusivity values. */ 110 private static final SparseBooleanArray sExclusivityValues = new SparseBooleanArray(); 111 static { sExclusivityValues.put(EXCLUSIVITY_NONE, true)112 sExclusivityValues.put(EXCLUSIVITY_NONE, true); sExclusivityValues.put(EXCLUSIVITY_USER, true)113 sExclusivityValues.put(EXCLUSIVITY_USER, true); sExclusivityValues.put(EXCLUSIVITY_PROFILE_GROUP, RoleFlags.isProfileGroupExclusivityAvailable())114 sExclusivityValues.put(EXCLUSIVITY_PROFILE_GROUP, 115 RoleFlags.isProfileGroupExclusivityAvailable()); 116 } 117 118 /** 119 * The name of this role. Must be unique. 120 */ 121 @NonNull 122 private final String mName; 123 124 /** 125 * Whether this role allows bypassing role holder qualification. 126 */ 127 private final boolean mAllowBypassingQualification; 128 129 /** 130 * The behavior of this role. 131 */ 132 @Nullable 133 private final RoleBehavior mBehavior; 134 135 @Nullable 136 private final String mDefaultHoldersResourceName; 137 138 /** 139 * The string resource for the description of this role. 140 */ 141 @StringRes 142 private final int mDescriptionResource; 143 144 /** 145 * The exclusivity of this role, i.e. whether this role allows multiple holders, or allows at 146 * most one holder within a user or a profile group. 147 */ 148 private final int mExclusivity; 149 150 /** 151 * Whether this role should fall back to the default holder. 152 */ 153 private final boolean mFallBackToDefaultHolder; 154 155 /** 156 * The feature flag for this app op to be granted, or {@code null} if none. 157 */ 158 @Nullable 159 private final Supplier<Boolean> mFeatureFlag; 160 161 /** 162 * The string resource for the label of this role. 163 */ 164 @StringRes 165 private final int mLabelResource; 166 167 /** 168 * The maximum SDK version for this role to be available. 169 */ 170 private final int mMaxSdkVersion; 171 172 /** 173 * The minimum SDK version for this role to be available. 174 */ 175 private final int mMinSdkVersion; 176 177 /** 178 * Whether this role should only grant privileges when a role holder is actively added. 179 */ 180 private final boolean mOnlyGrantWhenAdded; 181 182 /** 183 * Whether this role should override user's choice about privileges when granting. 184 */ 185 private final boolean mOverrideUserWhenGranting; 186 187 /** 188 * The string resource for the request description of this role, shown below the selected app in 189 * the request role dialog. 190 */ 191 @StringRes 192 private final int mRequestDescriptionResource; 193 194 /** 195 * The string resource for the request title of this role, shown as the title of the request 196 * role dialog. 197 */ 198 @StringRes 199 private final int mRequestTitleResource; 200 201 /** 202 * Whether this role is requestable by applications with 203 * {@link android.app.role.RoleManager#createRequestRoleIntent(String)}. 204 */ 205 private final boolean mRequestable; 206 207 /** 208 * The string resource for search keywords of this role, in addition to the label of this role, 209 * if it's non-zero. 210 */ 211 @StringRes 212 private final int mSearchKeywordsResource; 213 214 /** 215 * The string resource for the short label of this role, currently used when in a list of roles. 216 */ 217 @StringRes 218 private final int mShortLabelResource; 219 220 /** 221 * Whether the UI for this role will show the "None" item. Only valid if this role is 222 * {@link #isExclusive()}, and {@link #getFallbackHolder(Context)} should 223 * also return empty to allow actually selecting "None". 224 */ 225 private final boolean mShowNone; 226 227 /** 228 * Whether this role is static, i.e. the role will always be assigned to its default holders. 229 */ 230 private final boolean mStatic; 231 232 /** 233 * Whether this role only accepts system apps as its holders. 234 */ 235 private final boolean mSystemOnly; 236 237 /** 238 * Whether this role is visible to user. 239 */ 240 private final boolean mVisible; 241 242 /** 243 * The required components for an application to qualify for this role. 244 */ 245 @NonNull 246 private final List<RequiredComponent> mRequiredComponents; 247 248 /** 249 * The permissions to be granted by this role. 250 */ 251 @NonNull 252 private final List<Permission> mPermissions; 253 254 /** 255 * The app op permissions to be granted by this role. 256 */ 257 @NonNull 258 private final List<Permission> mAppOpPermissions; 259 260 /** 261 * The app ops to be set to allowed by this role. 262 */ 263 @NonNull 264 private final List<AppOp> mAppOps; 265 266 /** 267 * The set of preferred {@code Activity} configurations to be configured by this role. 268 */ 269 @NonNull 270 private final List<PreferredActivity> mPreferredActivities; 271 272 @Nullable 273 private final String mUiBehaviorName; 274 Role(@onNull String name, boolean allowBypassingQualification, @Nullable RoleBehavior behavior, @Nullable String defaultHoldersResourceName, @StringRes int descriptionResource, @Exclusivity int exclusivity, boolean fallBackToDefaultHolder, @Nullable Supplier<Boolean> featureFlag, @StringRes int labelResource, int maxSdkVersion, int minSdkVersion, boolean onlyGrantWhenAdded, boolean overrideUserWhenGranting, @StringRes int requestDescriptionResource, @StringRes int requestTitleResource, boolean requestable, @StringRes int searchKeywordsResource, @StringRes int shortLabelResource, boolean showNone, boolean statik, boolean systemOnly, boolean visible, @NonNull List<RequiredComponent> requiredComponents, @NonNull List<Permission> permissions, @NonNull List<Permission> appOpPermissions, @NonNull List<AppOp> appOps, @NonNull List<PreferredActivity> preferredActivities, @Nullable String uiBehaviorName)275 public Role(@NonNull String name, boolean allowBypassingQualification, 276 @Nullable RoleBehavior behavior, @Nullable String defaultHoldersResourceName, 277 @StringRes int descriptionResource, @Exclusivity int exclusivity, 278 boolean fallBackToDefaultHolder, @Nullable Supplier<Boolean> featureFlag, 279 @StringRes int labelResource, int maxSdkVersion, int minSdkVersion, 280 boolean onlyGrantWhenAdded, boolean overrideUserWhenGranting, 281 @StringRes int requestDescriptionResource, @StringRes int requestTitleResource, 282 boolean requestable, @StringRes int searchKeywordsResource, 283 @StringRes int shortLabelResource, boolean showNone, boolean statik, boolean systemOnly, 284 boolean visible, @NonNull List<RequiredComponent> requiredComponents, 285 @NonNull List<Permission> permissions, @NonNull List<Permission> appOpPermissions, 286 @NonNull List<AppOp> appOps, @NonNull List<PreferredActivity> preferredActivities, 287 @Nullable String uiBehaviorName) { 288 mName = name; 289 mAllowBypassingQualification = allowBypassingQualification; 290 mBehavior = behavior; 291 mDefaultHoldersResourceName = defaultHoldersResourceName; 292 mDescriptionResource = descriptionResource; 293 mExclusivity = exclusivity; 294 mFallBackToDefaultHolder = fallBackToDefaultHolder; 295 mFeatureFlag = featureFlag; 296 mLabelResource = labelResource; 297 mMaxSdkVersion = maxSdkVersion; 298 mMinSdkVersion = minSdkVersion; 299 mOnlyGrantWhenAdded = onlyGrantWhenAdded; 300 mOverrideUserWhenGranting = overrideUserWhenGranting; 301 mRequestDescriptionResource = requestDescriptionResource; 302 mRequestTitleResource = requestTitleResource; 303 mRequestable = requestable; 304 mSearchKeywordsResource = searchKeywordsResource; 305 mShortLabelResource = shortLabelResource; 306 mShowNone = showNone; 307 mStatic = statik; 308 mSystemOnly = systemOnly; 309 mVisible = visible; 310 mRequiredComponents = requiredComponents; 311 mPermissions = permissions; 312 mAppOpPermissions = appOpPermissions; 313 mAppOps = appOps; 314 mPreferredActivities = preferredActivities; 315 mUiBehaviorName = uiBehaviorName; 316 } 317 318 @NonNull getName()319 public String getName() { 320 return mName; 321 } 322 323 @Nullable getBehavior()324 public RoleBehavior getBehavior() { 325 return mBehavior; 326 } 327 328 @StringRes getDescriptionResource()329 public int getDescriptionResource() { 330 return mDescriptionResource; 331 } 332 isExclusive()333 public boolean isExclusive() { 334 return getExclusivity() != EXCLUSIVITY_NONE; 335 } 336 337 @Exclusivity getExclusivity()338 public int getExclusivity() { 339 if (com.android.permission.flags.Flags.crossUserRoleEnabled() && mBehavior != null) { 340 Integer exclusivity = mBehavior.getExclusivity(); 341 if (exclusivity != null) { 342 if (!sExclusivityValues.get(exclusivity)) { 343 throw new IllegalArgumentException( 344 "Role " + mName + " has invalid exclusivity: " 345 + exclusivity); 346 } 347 if (mShowNone && exclusivity == EXCLUSIVITY_NONE) { 348 throw new IllegalArgumentException( 349 "Role " + mName + " cannot be non-exclusive when showNone is true: " 350 + exclusivity); 351 } 352 if (!mPreferredActivities.isEmpty() && exclusivity == EXCLUSIVITY_PROFILE_GROUP) { 353 throw new IllegalArgumentException( 354 "Role " + mName + " cannot have preferred activities when exclusivity is " 355 + "profileGroup"); 356 } 357 return exclusivity; 358 } 359 } 360 return mExclusivity; 361 } 362 363 @Nullable getFeatureFlag()364 public Supplier<Boolean> getFeatureFlag() { 365 return mFeatureFlag; 366 } 367 368 @StringRes getLabelResource()369 public int getLabelResource() { 370 return mLabelResource; 371 } 372 373 @StringRes getRequestDescriptionResource()374 public int getRequestDescriptionResource() { 375 return mRequestDescriptionResource; 376 } 377 378 @StringRes getRequestTitleResource()379 public int getRequestTitleResource() { 380 return mRequestTitleResource; 381 } 382 isRequestable()383 public boolean isRequestable() { 384 return mRequestable; 385 } 386 387 @StringRes getSearchKeywordsResource()388 public int getSearchKeywordsResource() { 389 return mSearchKeywordsResource; 390 } 391 392 @StringRes getShortLabelResource()393 public int getShortLabelResource() { 394 return mShortLabelResource; 395 } 396 397 /** 398 * @see #mOnlyGrantWhenAdded 399 */ shouldOnlyGrantWhenAdded()400 public boolean shouldOnlyGrantWhenAdded() { 401 return mOnlyGrantWhenAdded; 402 } 403 404 /** 405 * @see #mOverrideUserWhenGranting 406 */ shouldOverrideUserWhenGranting()407 public boolean shouldOverrideUserWhenGranting() { 408 return mOverrideUserWhenGranting; 409 } 410 411 /** 412 * @see #mShowNone 413 */ shouldShowNone()414 public boolean shouldShowNone() { 415 return mShowNone; 416 } 417 isVisible()418 public boolean isVisible() { 419 return mVisible; 420 } 421 422 @NonNull getRequiredComponents()423 public List<RequiredComponent> getRequiredComponents() { 424 return mRequiredComponents; 425 } 426 427 @NonNull getPermissions()428 public List<Permission> getPermissions() { 429 return mPermissions; 430 } 431 432 @NonNull getAppOpPermissions()433 public List<Permission> getAppOpPermissions() { 434 return mAppOpPermissions; 435 } 436 437 @NonNull getAppOps()438 public List<AppOp> getAppOps() { 439 return mAppOps; 440 } 441 442 @NonNull getPreferredActivities()443 public List<PreferredActivity> getPreferredActivities() { 444 return mPreferredActivities; 445 } 446 447 @Nullable getUiBehaviorName()448 public String getUiBehaviorName() { 449 return mUiBehaviorName; 450 } 451 452 /** 453 * Callback when this role is added to the system for the first time. 454 * 455 * @param user the user to add the role for 456 * @param context the {@code Context} to retrieve system services 457 */ onRoleAddedAsUser(@onNull UserHandle user, @NonNull Context context)458 public void onRoleAddedAsUser(@NonNull UserHandle user, @NonNull Context context) { 459 if (mBehavior != null) { 460 mBehavior.onRoleAddedAsUser(this, user, context); 461 } 462 } 463 464 /** 465 * Check whether this role is available. 466 * 467 * @param user the user to check for 468 * @param context the {@code Context} to retrieve system services 469 * 470 * @return whether this role is available. 471 */ isAvailableAsUser(@onNull UserHandle user, @NonNull Context context)472 public boolean isAvailableAsUser(@NonNull UserHandle user, @NonNull Context context) { 473 if (!isAvailableByFeatureFlagAndSdkVersion()) { 474 return false; 475 } 476 if (getExclusivity() == EXCLUSIVITY_PROFILE_GROUP) { 477 if (UserUtils.isPrivateProfile(user, context)) { 478 return false; 479 } 480 481 // A profile group exclusive role may only be available in a profile when it's 482 // available in the profile parent. 483 UserHandle profileParent = UserUtils.getProfileParentOrSelf(user, context); 484 if (!Objects.equals(user, profileParent) 485 && !isAvailableAsUser(profileParent, context)) { 486 return false; 487 } 488 } 489 if (mBehavior != null) { 490 return mBehavior.isAvailableAsUser(this, user, context); 491 } 492 return true; 493 } 494 495 /** 496 * Check whether this role is available based on SDK version. 497 * 498 * @return whether this role is available based on SDK version 499 */ 500 @VisibleForTesting isAvailableByFeatureFlagAndSdkVersion()501 public boolean isAvailableByFeatureFlagAndSdkVersion() { 502 if (mFeatureFlag != null && !mFeatureFlag.get()) { 503 return false; 504 } 505 return (Build.VERSION.SDK_INT >= mMinSdkVersion 506 // Workaround to match the value 36 for B in roles.xml before SDK finalization. 507 || (mMinSdkVersion == 36 && SdkLevel.isAtLeastB())) 508 && Build.VERSION.SDK_INT <= mMaxSdkVersion; 509 } 510 511 /** 512 * Check whether this role is static, which may change due to bypassing qualification. 513 * 514 * @param context the {@code Context} to retrieve system services 515 * @return whether this role is static 516 */ isStatic(@onNull Context context)517 public boolean isStatic(@NonNull Context context) { 518 return mStatic && !isBypassingQualification(context); 519 } 520 521 /** 522 * Get the default holders of this role, which will be added when the role is added for the 523 * first time. 524 * 525 * @param user the user of the role 526 * @param context the {@code Context} to retrieve system services 527 * @return the list of package names of the default holders 528 */ 529 @NonNull getDefaultHoldersAsUser(@onNull UserHandle user, @NonNull Context context)530 public List<String> getDefaultHoldersAsUser(@NonNull UserHandle user, 531 @NonNull Context context) { 532 // Do not allow default role holder for non-active user if the role is exclusive to profile 533 // group 534 if (isNonActiveUserForProfileGroupExclusiveRole(user, context)) { 535 return Collections.emptyList(); 536 } 537 538 if (mBehavior != null) { 539 List<String> defaultHolders = mBehavior.getDefaultHoldersAsUser(this, user, context); 540 if (defaultHolders != null) { 541 return defaultHolders; 542 } 543 } 544 545 if (mDefaultHoldersResourceName == null) { 546 return Collections.emptyList(); 547 } 548 549 Resources resources = context.getResources(); 550 int resourceId = resources.getIdentifier(mDefaultHoldersResourceName, "string", "android"); 551 if (resourceId == 0) { 552 Log.w(LOG_TAG, "Cannot find resource for default holder: " 553 + mDefaultHoldersResourceName); 554 return Collections.emptyList(); 555 } 556 557 String defaultHolders; 558 try { 559 defaultHolders = resources.getString(resourceId); 560 } catch (Resources.NotFoundException e) { 561 Log.w(LOG_TAG, "Cannot get resource for default holder: " + mDefaultHoldersResourceName, 562 e); 563 return Collections.emptyList(); 564 } 565 if (TextUtils.isEmpty(defaultHolders)) { 566 return Collections.emptyList(); 567 } 568 569 if (isExclusive()) { 570 return CollectionUtils.singletonOrEmpty( 571 SignedPackageUtils.getPackageNameAsUser(defaultHolders, user, context)); 572 } else { 573 return SignedPackageUtils.getPackageNamesAsUser(defaultHolders, user, context); 574 } 575 } 576 577 /** 578 * Get the fallback holder of this role, which will be added whenever there are no role holders. 579 * <p> 580 * Should return {@code null} if this role {@link #mShowNone shows a "None" item}. 581 * 582 * @param user the user of the role 583 * @param context the {@code Context} to retrieve system services 584 * @return the package name of the fallback holder, or {@code null} if none 585 */ 586 @Nullable getFallbackHolderAsUser(@onNull UserHandle user, @NonNull Context context)587 public String getFallbackHolderAsUser(@NonNull UserHandle user, @NonNull Context context) { 588 if (!RoleManagerCompat.isRoleFallbackEnabledAsUser(this, user, context)) { 589 return null; 590 } 591 // Do not fall back for non-active user if the role is exclusive to profile group 592 if (isNonActiveUserForProfileGroupExclusiveRole(user, context)) { 593 return null; 594 } 595 if (mFallBackToDefaultHolder) { 596 return CollectionUtils.firstOrNull(getDefaultHoldersAsUser(user, context)); 597 } 598 if (mBehavior != null) { 599 return mBehavior.getFallbackHolderAsUser(this, user, context); 600 } 601 return null; 602 } 603 isNonActiveUserForProfileGroupExclusiveRole(@onNull UserHandle user, @NonNull Context context)604 private boolean isNonActiveUserForProfileGroupExclusiveRole(@NonNull UserHandle user, 605 @NonNull Context context) { 606 if (RoleFlags.isProfileGroupExclusivityAvailable() 607 && getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) { 608 Context userContext = UserUtils.getUserContext(context, user); 609 RoleManager userRoleManager = userContext.getSystemService(RoleManager.class); 610 return !Objects.equals(userRoleManager.getActiveUserForRole(mName), user); 611 } 612 return false; 613 } 614 615 /** 616 * Check whether this role is allowed to bypass qualification, if enabled globally. 617 * 618 * @param context the {@code Context} to retrieve system services 619 * 620 * @return whether this role is allowed to bypass qualification 621 */ shouldAllowBypassingQualification(@onNull Context context)622 public boolean shouldAllowBypassingQualification(@NonNull Context context) { 623 if (mBehavior != null) { 624 Boolean allowBypassingQualification = mBehavior.shouldAllowBypassingQualification(this, 625 context); 626 if (allowBypassingQualification != null) { 627 return allowBypassingQualification; 628 } 629 } 630 return mAllowBypassingQualification; 631 } 632 isBypassingQualification(@onNull Context context)633 private boolean isBypassingQualification(@NonNull Context context) { 634 RoleManager roleManager = context.getSystemService(RoleManager.class); 635 return shouldAllowBypassingQualification(context) 636 && RoleManagerCompat.isBypassingRoleQualification(roleManager); 637 } 638 639 /** 640 * Check whether a package is qualified for this role, i.e. whether it contains all the required 641 * components (plus meeting some other general restrictions). 642 * 643 * @param packageName the package name to check for 644 * @param user the user to check for 645 * @param context the {@code Context} to retrieve system services 646 * 647 * @return whether the package is qualified for a role 648 */ isPackageQualifiedAsUser(@onNull String packageName, @NonNull UserHandle user, @NonNull Context context)649 public boolean isPackageQualifiedAsUser(@NonNull String packageName, @NonNull UserHandle user, 650 @NonNull Context context) { 651 if (isBypassingQualification(context)) { 652 return true; 653 } 654 655 ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName, user, 656 context); 657 if (applicationInfo == null) { 658 Log.w(LOG_TAG, "Cannot get ApplicationInfo for package: " + packageName); 659 return false; 660 } 661 if (!isPackageMinimallyQualifiedAsUser(applicationInfo, user, context)) { 662 return false; 663 } 664 665 if (mBehavior != null) { 666 Boolean isPackageQualified = mBehavior.isPackageQualifiedAsUser(this, packageName, 667 user, context); 668 if (isPackageQualified != null) { 669 return isPackageQualified; 670 } 671 } 672 673 int requiredComponentsSize = mRequiredComponents.size(); 674 for (int i = 0; i < requiredComponentsSize; i++) { 675 RequiredComponent requiredComponent = mRequiredComponents.get(i); 676 677 if (!requiredComponent.isRequired(applicationInfo)) { 678 continue; 679 } 680 681 if (requiredComponent.getQualifyingComponentForPackageAsUser(packageName, user, context) 682 == null) { 683 Log.i(LOG_TAG, packageName + " not qualified for " + mName 684 + " due to missing " + requiredComponent); 685 return false; 686 } 687 } 688 689 if (mStatic && !getDefaultHoldersAsUser(user, context).contains(packageName)) { 690 return false; 691 } 692 693 return true; 694 } 695 696 /** 697 * Get the list of packages that are qualified for this role, i.e. packages containing all the 698 * required components (plus meeting some other general restrictions). 699 * 700 * @param user the user to get the qualifying packages. 701 * @param context the {@code Context} to retrieve system services 702 * 703 * @return the list of packages that are qualified for this role 704 */ 705 @NonNull getQualifyingPackagesAsUser(@onNull UserHandle user, @NonNull Context context)706 public List<String> getQualifyingPackagesAsUser(@NonNull UserHandle user, 707 @NonNull Context context) { 708 List<String> qualifyingPackages = null; 709 710 if (mBehavior != null) { 711 qualifyingPackages = mBehavior.getQualifyingPackagesAsUser(this, user, context); 712 } 713 714 ArrayMap<String, ApplicationInfo> packageApplicationInfoMap = new ArrayMap<>(); 715 if (qualifyingPackages == null) { 716 ArrayMap<String, ArraySet<RequiredComponent>> packageRequiredComponentsMap = 717 new ArrayMap<>(); 718 int requiredComponentsSize = mRequiredComponents.size(); 719 for (int requiredComponentsIndex = 0; requiredComponentsIndex < requiredComponentsSize; 720 requiredComponentsIndex++) { 721 RequiredComponent requiredComponent = mRequiredComponents.get( 722 requiredComponentsIndex); 723 724 if (!requiredComponent.isAvailable()) { 725 continue; 726 } 727 728 // This returns at most one component per package. 729 List<ComponentName> qualifyingComponents = 730 requiredComponent.getQualifyingComponentsAsUser(user, context); 731 int qualifyingComponentsSize = qualifyingComponents.size(); 732 for (int qualifyingComponentsIndex = 0; 733 qualifyingComponentsIndex < qualifyingComponentsSize; 734 ++qualifyingComponentsIndex) { 735 ComponentName componentName = qualifyingComponents.get( 736 qualifyingComponentsIndex); 737 738 String packageName = componentName.getPackageName(); 739 ArraySet<RequiredComponent> packageRequiredComponents = 740 packageRequiredComponentsMap.get(packageName); 741 if (packageRequiredComponents == null) { 742 packageRequiredComponents = new ArraySet<>(); 743 packageRequiredComponentsMap.put(packageName, packageRequiredComponents); 744 } 745 packageRequiredComponents.add(requiredComponent); 746 } 747 } 748 749 qualifyingPackages = new ArrayList<>(); 750 int packageRequiredComponentsMapSize = packageRequiredComponentsMap.size(); 751 for (int packageRequiredComponentsMapIndex = 0; 752 packageRequiredComponentsMapIndex < packageRequiredComponentsMapSize; 753 packageRequiredComponentsMapIndex++) { 754 String packageName = packageRequiredComponentsMap.keyAt( 755 packageRequiredComponentsMapIndex); 756 ArraySet<RequiredComponent> packageRequiredComponents = 757 packageRequiredComponentsMap.valueAt(packageRequiredComponentsMapIndex); 758 759 ApplicationInfo applicationInfo = packageApplicationInfoMap.get(packageName); 760 if (applicationInfo == null) { 761 applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName, user, 762 context); 763 if (applicationInfo == null) { 764 Log.w(LOG_TAG, "Cannot get ApplicationInfo for package: " + packageName 765 + ", user: " + user.getIdentifier()); 766 continue; 767 } 768 packageApplicationInfoMap.put(packageName, applicationInfo); 769 } 770 771 boolean hasAllRequiredComponents = true; 772 for (int requiredComponentsIndex = 0; 773 requiredComponentsIndex < requiredComponentsSize; 774 requiredComponentsIndex++) { 775 RequiredComponent requiredComponent = mRequiredComponents.get( 776 requiredComponentsIndex); 777 778 if (!requiredComponent.isRequired(applicationInfo)) { 779 continue; 780 } 781 782 if (!packageRequiredComponents.contains(requiredComponent)) { 783 hasAllRequiredComponents = false; 784 break; 785 } 786 } 787 788 if (hasAllRequiredComponents) { 789 qualifyingPackages.add(packageName); 790 } 791 } 792 } 793 794 int qualifyingPackagesSize = qualifyingPackages.size(); 795 for (int i = 0; i < qualifyingPackagesSize; ) { 796 String packageName = qualifyingPackages.get(i); 797 798 ApplicationInfo applicationInfo = packageApplicationInfoMap.get(packageName); 799 if (applicationInfo == null) { 800 applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName, user, 801 context); 802 if (applicationInfo == null) { 803 Log.w(LOG_TAG, "Cannot get ApplicationInfo for package: " + packageName 804 + ", user: " + user.getIdentifier()); 805 continue; 806 } 807 packageApplicationInfoMap.put(packageName, applicationInfo); 808 } 809 810 if (!isPackageMinimallyQualifiedAsUser(applicationInfo, user, context)) { 811 qualifyingPackages.remove(i); 812 qualifyingPackagesSize--; 813 } else { 814 i++; 815 } 816 } 817 818 return qualifyingPackages; 819 } 820 isPackageMinimallyQualifiedAsUser(@onNull ApplicationInfo applicationInfo, @NonNull UserHandle user, @NonNull Context context)821 private boolean isPackageMinimallyQualifiedAsUser(@NonNull ApplicationInfo applicationInfo, 822 @NonNull UserHandle user, 823 @NonNull Context context) { 824 String packageName = applicationInfo.packageName; 825 if (Objects.equals(packageName, PACKAGE_NAME_ANDROID_SYSTEM)) { 826 return false; 827 } 828 829 if (mSystemOnly && (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { 830 return false; 831 } 832 833 if (!applicationInfo.enabled) { 834 return false; 835 } 836 837 if (applicationInfo.isInstantApp()) { 838 return false; 839 } 840 841 PackageManager userPackageManager = UserUtils.getUserContext(context, user) 842 .getPackageManager(); 843 List<SharedLibraryInfo> declaredLibraries = userPackageManager.getDeclaredSharedLibraries( 844 packageName, 0); 845 final int libCount = declaredLibraries.size(); 846 for (int i = 0; i < libCount; i++) { 847 SharedLibraryInfo sharedLibrary = declaredLibraries.get(i); 848 if (sharedLibrary.getType() != SharedLibraryInfo.TYPE_DYNAMIC) { 849 return false; 850 } 851 } 852 853 return true; 854 } 855 856 /** 857 * Grant this role to an application. 858 * 859 * @param packageName the package name of the application to be granted this role to 860 * @param dontKillApp whether this application should not be killed despite changes 861 * @param overrideUser whether to override user when granting privileges 862 * @param user the user of the application 863 * @param context the {@code Context} to retrieve system services 864 */ grantAsUser(@onNull String packageName, boolean dontKillApp, boolean overrideUser, @NonNull UserHandle user, @NonNull Context context)865 public void grantAsUser(@NonNull String packageName, boolean dontKillApp, 866 boolean overrideUser, @NonNull UserHandle user, @NonNull Context context) { 867 boolean permissionOrAppOpChanged = Permissions.grantAsUser(packageName, 868 Permissions.filterBySdkVersionAsUser(mPermissions, user, context), 869 SdkLevel.isAtLeastS() ? !mSystemOnly : true, overrideUser, true, false, false, 870 user, context); 871 872 List<String> appOpPermissionsToGrant = 873 Permissions.filterBySdkVersionAsUser(mAppOpPermissions, user, context); 874 int appOpPermissionsSize = appOpPermissionsToGrant.size(); 875 for (int i = 0; i < appOpPermissionsSize; i++) { 876 String appOpPermission = appOpPermissionsToGrant.get(i); 877 AppOpPermissions.grantAsUser(packageName, appOpPermission, overrideUser, user, context); 878 } 879 880 int appOpsSize = mAppOps.size(); 881 for (int i = 0; i < appOpsSize; i++) { 882 AppOp appOp = mAppOps.get(i); 883 appOp.grantAsUser(packageName, user, context); 884 } 885 886 int preferredActivitiesSize = mPreferredActivities.size(); 887 for (int i = 0; i < preferredActivitiesSize; i++) { 888 PreferredActivity preferredActivity = mPreferredActivities.get(i); 889 preferredActivity.configureAsUser(packageName, user, context); 890 } 891 892 if (mBehavior != null) { 893 mBehavior.grantAsUser(this, packageName, user, context); 894 } 895 896 if (!dontKillApp && permissionOrAppOpChanged 897 && !Permissions.isRuntimePermissionsSupportedAsUser(packageName, user, context)) { 898 killAppAsUser(packageName, user, context); 899 } 900 } 901 902 /** 903 * Revoke this role from an application. 904 * 905 * @param packageName the package name of the application to be granted this role to 906 * @param dontKillApp whether this application should not be killed despite changes 907 * @param overrideSystemFixedPermissions whether system-fixed permissions can be revoked 908 * @param user the user of the role 909 * @param context the {@code Context} to retrieve system services 910 */ revokeAsUser(@onNull String packageName, boolean dontKillApp, boolean overrideSystemFixedPermissions, @NonNull UserHandle user, @NonNull Context context)911 public void revokeAsUser(@NonNull String packageName, boolean dontKillApp, 912 boolean overrideSystemFixedPermissions, @NonNull UserHandle user, 913 @NonNull Context context) { 914 Context userContext = UserUtils.getUserContext(context, user); 915 RoleManager userRoleManager = userContext.getSystemService(RoleManager.class); 916 List<String> otherRoleNames = userRoleManager.getHeldRolesFromController(packageName); 917 otherRoleNames.remove(mName); 918 919 List<String> permissionsToRevoke = 920 Permissions.filterBySdkVersionAsUser(mPermissions, user, context); 921 ArrayMap<String, Role> roles = Roles.get(context); 922 int otherRoleNamesSize = otherRoleNames.size(); 923 for (int i = 0; i < otherRoleNamesSize; i++) { 924 String roleName = otherRoleNames.get(i); 925 Role role = roles.get(roleName); 926 permissionsToRevoke.removeAll( 927 Permissions.filterBySdkVersionAsUser(role.mPermissions, user, context)); 928 } 929 930 boolean permissionOrAppOpChanged = Permissions.revokeAsUser(packageName, 931 permissionsToRevoke, true, false, overrideSystemFixedPermissions, user, context); 932 933 List<String> appOpPermissionsToRevoke = Permissions.filterBySdkVersionAsUser( 934 mAppOpPermissions, user, context); 935 for (int i = 0; i < otherRoleNamesSize; i++) { 936 String roleName = otherRoleNames.get(i); 937 Role role = roles.get(roleName); 938 appOpPermissionsToRevoke.removeAll( 939 Permissions.filterBySdkVersionAsUser(role.mAppOpPermissions, user, context)); 940 } 941 int appOpPermissionsSize = appOpPermissionsToRevoke.size(); 942 for (int i = 0; i < appOpPermissionsSize; i++) { 943 String appOpPermission = appOpPermissionsToRevoke.get(i); 944 AppOpPermissions.revokeAsUser(packageName, appOpPermission, user, context); 945 } 946 947 List<AppOp> appOpsToRevoke = new ArrayList<>(mAppOps); 948 for (int i = 0; i < otherRoleNamesSize; i++) { 949 String roleName = otherRoleNames.get(i); 950 Role role = roles.get(roleName); 951 appOpsToRevoke.removeAll(role.mAppOps); 952 } 953 int appOpsSize = appOpsToRevoke.size(); 954 for (int i = 0; i < appOpsSize; i++) { 955 AppOp appOp = appOpsToRevoke.get(i); 956 appOp.revokeAsUser(packageName, user, context); 957 } 958 959 // TODO: Revoke preferred activities? But this is unnecessary for most roles using it as 960 // they have fallback holders. Moreover, clearing the preferred activity might result in 961 // other system components listening to preferred activity change get notified for the 962 // wrong thing when we are removing a exclusive role holder for adding another. 963 964 if (mBehavior != null) { 965 mBehavior.revokeAsUser(this, packageName, user, context); 966 } 967 968 if (!dontKillApp && permissionOrAppOpChanged) { 969 killAppAsUser(packageName, user, context); 970 } 971 } 972 killAppAsUser(@onNull String packageName, @NonNull UserHandle user, @NonNull Context context)973 private void killAppAsUser(@NonNull String packageName, @NonNull UserHandle user, 974 @NonNull Context context) { 975 if (DEBUG) { 976 Log.i(LOG_TAG, "Killing " + packageName + " due to " 977 + Thread.currentThread().getStackTrace()[3].getMethodName() 978 + "(" + mName + ")"); 979 } 980 ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName, user, 981 context); 982 if (applicationInfo == null) { 983 Log.w(LOG_TAG, "Cannot get ApplicationInfo for package: " + packageName); 984 return; 985 } 986 ActivityManager activityManager = context.getSystemService(ActivityManager.class); 987 activityManager.killUid(applicationInfo.uid, "Permission or app op changed"); 988 } 989 990 /** 991 * Callback when a role holder (other than "none") was added. 992 * 993 * @param packageName the package name of the role holder 994 * @param user the user for the role 995 * @param context the {@code Context} to retrieve system services 996 */ onHolderAddedAsUser(@onNull String packageName, @NonNull UserHandle user, @NonNull Context context)997 public void onHolderAddedAsUser(@NonNull String packageName, @NonNull UserHandle user, 998 @NonNull Context context) { 999 if (RoleFlags.isProfileGroupExclusivityAvailable() 1000 && com.android.permission.flags.Flags.crossUserRoleUxBugfixEnabled() 1001 && getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) { 1002 UserHandle profileParent = UserUtils.getProfileParentOrSelf(user, context); 1003 RoleManagerCompat.setRoleFallbackEnabledAsUser(this, true, profileParent, context); 1004 } else { 1005 RoleManagerCompat.setRoleFallbackEnabledAsUser(this, true, user, context); 1006 } 1007 } 1008 1009 /** 1010 * Callback when a role holder (other than "none") was selected in the UI and added 1011 * successfully. 1012 * 1013 * @param packageName the package name of the role holder 1014 * @param user the user for the role 1015 * @param context the {@code Context} to retrieve system services 1016 */ onHolderSelectedAsUser(@onNull String packageName, @NonNull UserHandle user, @NonNull Context context)1017 public void onHolderSelectedAsUser(@NonNull String packageName, @NonNull UserHandle user, 1018 @NonNull Context context) { 1019 if (mBehavior != null) { 1020 mBehavior.onHolderSelectedAsUser(this, packageName, user, context); 1021 } 1022 } 1023 1024 /** 1025 * Callback when a role holder changed. 1026 * 1027 * @param user the user for the role 1028 * @param context the {@code Context} to retrieve system services 1029 */ onHolderChangedAsUser(@onNull UserHandle user, @NonNull Context context)1030 public void onHolderChangedAsUser(@NonNull UserHandle user, 1031 @NonNull Context context) { 1032 if (mBehavior != null) { 1033 mBehavior.onHolderChangedAsUser(this, user, context); 1034 } 1035 } 1036 1037 /** 1038 * Callback when the "none" role holder was selected in the UI. 1039 * 1040 * @param user the user for the role 1041 * @param context the {@code Context} to retrieve system services 1042 */ onNoneHolderSelectedAsUser(@onNull UserHandle user, @NonNull Context context)1043 public void onNoneHolderSelectedAsUser(@NonNull UserHandle user, @NonNull Context context) { 1044 RoleManagerCompat.setRoleFallbackEnabledAsUser(this, false, user, context); 1045 if (RoleFlags.isProfileGroupExclusivityAvailable() 1046 && getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) { 1047 RoleManager roleManager = context.getSystemService(RoleManager.class); 1048 roleManager.setActiveUserForRole(mName, user, 0); 1049 } 1050 } 1051 1052 /** 1053 * Check whether this role should be visible to user. 1054 * 1055 * @param user the user to check for 1056 * @param context the {@code Context} to retrieve system services 1057 * 1058 * @return whether this role should be visible to user 1059 */ isVisibleAsUser(@onNull UserHandle user, @NonNull Context context)1060 public boolean isVisibleAsUser(@NonNull UserHandle user, @NonNull Context context) { 1061 if (mBehavior != null) { 1062 Boolean isVisibleAsUser = mBehavior.isVisibleAsUser(this, user, context); 1063 if (isVisibleAsUser != null) { 1064 if (isVisibleAsUser && mStatic) { 1065 throw new IllegalArgumentException("static=\"true\" is invalid for a visible " 1066 + "role: " + mName); 1067 } 1068 if (isVisibleAsUser && (mDescriptionResource == 0 1069 || mLabelResource == 0 1070 || mShortLabelResource == 0)) { 1071 throw new IllegalArgumentException("description, label, and shortLabel are " 1072 + "required for a visible role: " + mName); 1073 } 1074 return isVisibleAsUser; 1075 } 1076 } 1077 return isVisible(); 1078 } 1079 1080 /** 1081 * Check whether a qualifying application should be visible to user. 1082 * 1083 * @param applicationInfo the {@link ApplicationInfo} for the application 1084 * @param user the user for the application 1085 * @param context the {@code Context} to retrieve system services 1086 * 1087 * @return whether the qualifying application should be visible to user 1088 */ isApplicationVisibleAsUser(@onNull ApplicationInfo applicationInfo, @NonNull UserHandle user, @NonNull Context context)1089 public boolean isApplicationVisibleAsUser(@NonNull ApplicationInfo applicationInfo, 1090 @NonNull UserHandle user, @NonNull Context context) { 1091 RoleBehavior behavior = getBehavior(); 1092 if (behavior == null) { 1093 return true; 1094 } 1095 return behavior.isApplicationVisibleAsUser(this, applicationInfo, user, context); 1096 } 1097 1098 /** 1099 * Check whether this role is restricted and return the {@code Intent} for the restriction if it 1100 * is. 1101 * <p> 1102 * If a role is restricted, it is implied that all applications are restricted for the role as 1103 * well. 1104 * 1105 * @param user the user to check for 1106 * @param context the {@code Context} to retrieve system services 1107 * 1108 * @return the {@code Intent} for the restriction if this role is restricted, or {@code null} 1109 * otherwise. 1110 */ 1111 @Nullable getRestrictionIntentAsUser(@onNull UserHandle user, @NonNull Context context)1112 public Intent getRestrictionIntentAsUser(@NonNull UserHandle user, @NonNull Context context) { 1113 if (SdkLevel.isAtLeastU() && isExclusive()) { 1114 boolean crossUserRoleUxBugfixEnabled = 1115 com.android.permission.flags.Flags.crossUserRoleUxBugfixEnabled(); 1116 if (crossUserRoleUxBugfixEnabled && getExclusivity() == EXCLUSIVITY_PROFILE_GROUP) { 1117 DevicePolicyManager devicePolicyManager = 1118 context.getSystemService(DevicePolicyManager.class); 1119 if (!devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) { 1120 // For profileGroup exclusive roles users on BYOD are free to choose personal or 1121 // work profile app regardless of DISALLOW_CONFIG_DEFAULT_APPS 1122 return null; 1123 } 1124 } 1125 1126 // Otherwise if role is profileGroup exclusive check DISALLOW_CONFIG_DEFAULT_APPS for 1127 // all users 1128 List<UserHandle> profiles = 1129 (crossUserRoleUxBugfixEnabled && getExclusivity() == EXCLUSIVITY_PROFILE_GROUP) 1130 ? UserUtils.getUserProfiles(user, context, true) 1131 : List.of(user); 1132 final int profilesSize = profiles.size(); 1133 for (int i = 0; i < profilesSize; i++) { 1134 UserHandle profile = profiles.get(i); 1135 UserManager userManager = context.getSystemService(UserManager.class); 1136 if (userManager.hasUserRestrictionForUser( 1137 UserManager.DISALLOW_CONFIG_DEFAULT_APPS, profile)) { 1138 return new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS) 1139 .putExtra( 1140 DevicePolicyManager.EXTRA_RESTRICTION, 1141 UserManager.DISALLOW_CONFIG_DEFAULT_APPS) 1142 .putExtra(Intent.EXTRA_USER, profile) 1143 .putExtra(IntentCompat.EXTRA_USER_ID, profile.getIdentifier()); 1144 } 1145 } 1146 } 1147 return null; 1148 } 1149 1150 /** 1151 * Check whether an application is restricted for this role and return the {@code Intent} for 1152 * the restriction if it is. 1153 * <p> 1154 * If a role is restricted, it is implied that all applications are restricted for the role as 1155 * well. 1156 * 1157 * @param applicationInfo the {@link ApplicationInfo} for the application 1158 * @param user the user to check for 1159 * @param context the {@code Context} to retrieve system services 1160 * 1161 * @return the {@code Intent} for the restriction if the application is restricted for this 1162 * role, or {@code null} otherwise. 1163 */ 1164 @Nullable getApplicationRestrictionIntentAsUser(@onNull ApplicationInfo applicationInfo, @NonNull UserHandle user, @NonNull Context context)1165 public Intent getApplicationRestrictionIntentAsUser(@NonNull ApplicationInfo applicationInfo, 1166 @NonNull UserHandle user, @NonNull Context context) { 1167 if (SdkLevel.isAtLeastV() && Flags.enhancedConfirmationModeApisEnabled()) { 1168 Context userContext = UserUtils.getUserContext(context, user); 1169 EnhancedConfirmationManager userEnhancedConfirmationManager = 1170 userContext.getSystemService(EnhancedConfirmationManager.class); 1171 String packageName = applicationInfo.packageName; 1172 boolean isRestricted; 1173 try { 1174 isRestricted = userEnhancedConfirmationManager.isRestricted(packageName, mName); 1175 } catch (PackageManager.NameNotFoundException e) { 1176 Log.w(LOG_TAG, "Cannot check enhanced confirmation restriction for package: " 1177 + packageName, e); 1178 isRestricted = false; 1179 } 1180 if (isRestricted) { 1181 try { 1182 return userEnhancedConfirmationManager.createRestrictedSettingDialogIntent( 1183 packageName, mName); 1184 } catch (PackageManager.NameNotFoundException e) { 1185 Log.w(LOG_TAG, "Cannot create enhanced confirmation restriction intent for" 1186 + " package: " + packageName, e); 1187 } 1188 } 1189 } 1190 return getRestrictionIntentAsUser(user, context); 1191 } 1192 1193 @Override toString()1194 public String toString() { 1195 return "Role{" 1196 + "mName='" + mName + '\'' 1197 + ", mAllowBypassingQualification=" + mAllowBypassingQualification 1198 + ", mBehavior=" + mBehavior 1199 + ", mDefaultHoldersResourceName=" + mDefaultHoldersResourceName 1200 + ", mDescriptionResource=" + mDescriptionResource 1201 + ", mExclusivity=" + mExclusivity 1202 + ", mFallBackToDefaultHolder=" + mFallBackToDefaultHolder 1203 + ", mFeatureFlag=" + mFeatureFlag 1204 + ", mLabelResource=" + mLabelResource 1205 + ", mMaxSdkVersion=" + mMaxSdkVersion 1206 + ", mMinSdkVersion=" + mMinSdkVersion 1207 + ", mOnlyGrantWhenAdded=" + mOnlyGrantWhenAdded 1208 + ", mOverrideUserWhenGranting=" + mOverrideUserWhenGranting 1209 + ", mRequestDescriptionResource=" + mRequestDescriptionResource 1210 + ", mRequestTitleResource=" + mRequestTitleResource 1211 + ", mRequestable=" + mRequestable 1212 + ", mSearchKeywordsResource=" + mSearchKeywordsResource 1213 + ", mShortLabelResource=" + mShortLabelResource 1214 + ", mShowNone=" + mShowNone 1215 + ", mStatic=" + mStatic 1216 + ", mSystemOnly=" + mSystemOnly 1217 + ", mVisible=" + mVisible 1218 + ", mRequiredComponents=" + mRequiredComponents 1219 + ", mPermissions=" + mPermissions 1220 + ", mAppOpPermissions=" + mAppOpPermissions 1221 + ", mAppOps=" + mAppOps 1222 + ", mPreferredActivities=" + mPreferredActivities 1223 + ", mUiBehaviorName=" + mUiBehaviorName 1224 + '}'; 1225 } 1226 } 1227