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.packageinstaller.role.model;
18 
19 import android.app.ActivityManager;
20 import android.app.role.RoleManager;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.PackageManager;
26 import android.os.Process;
27 import android.os.UserHandle;
28 import android.util.ArrayMap;
29 import android.util.Log;
30 
31 import androidx.annotation.NonNull;
32 import androidx.annotation.Nullable;
33 import androidx.annotation.StringRes;
34 import androidx.preference.Preference;
35 
36 import com.android.packageinstaller.Constants;
37 import com.android.packageinstaller.permission.utils.Utils;
38 import com.android.packageinstaller.role.ui.TwoTargetPreference;
39 import com.android.packageinstaller.role.utils.PackageUtils;
40 import com.android.packageinstaller.role.utils.UserUtils;
41 
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.List;
45 import java.util.Objects;
46 
47 /**
48  * Specifies a role and its properties.
49  * <p>
50  * A role is a unique name within the system associated with certain privileges. There can be
51  * multiple applications qualifying for a role, but only a subset of them can become role holders.
52  * To qualify for a role, an application must meet certain requirements, including defining certain
53  * components in its manifest. Then the application will need user consent to become the role
54  * holder.
55  * <p>
56  * Upon becoming a role holder, the application may be granted certain permissions, have certain
57  * app ops set to certain modes and certain {@code Activity} components configured as preferred for
58  * certain {@code Intent} actions. When an application loses its role, these privileges will also be
59  * revoked.
60  *
61  * @see android.app.role.RoleManager
62  */
63 public class Role {
64 
65     private static final String LOG_TAG = Role.class.getSimpleName();
66 
67     private static final boolean DEBUG = false;
68 
69     private static final String PACKAGE_NAME_ANDROID_SYSTEM = "android";
70 
71     /**
72      * The name of this role. Must be unique.
73      */
74     @NonNull
75     private final String mName;
76 
77     /**
78      * The behavior of this role.
79      */
80     @Nullable
81     private final RoleBehavior mBehavior;
82 
83     /**
84      * The string resource for the description of this role.
85      */
86     @StringRes
87     private final int mDescriptionResource;
88 
89     /**
90      * Whether this role is exclusive, i.e. allows at most one holder.
91      */
92     private final boolean mExclusive;
93 
94     /**
95      * The string resource for the label of this role.
96      */
97     @StringRes
98     private final int mLabelResource;
99 
100     /**
101      * The string resource for the request description of this role, shown below the selected app in
102      * the request role dialog.
103      */
104     @StringRes
105     private final int mRequestDescriptionResource;
106 
107     /**
108      * The string resource for the request title of this role, shown as the title of the request
109      * role dialog.
110      */
111     @StringRes
112     private final int mRequestTitleResource;
113 
114     /**
115      * Whether this role is requestable by applications with
116      * {@link android.app.role.RoleManager#createRequestRoleIntent(String)}.
117      */
118     private final boolean mRequestable;
119 
120     /**
121      * The string resource for the short label of this role, currently used when in a list of roles.
122      */
123     @StringRes
124     private final int mShortLabelResource;
125 
126     /**
127      * Whether the UI for this role will show the "None" item. Only valid if this role is
128      * {@link #mExclusive exclusive}, and {@link #getFallbackHolder(Context)} should also return
129      * empty to allow actually selecting "None".
130      */
131     private final boolean mShowNone;
132 
133     /**
134      * Whether this role only accepts system apps as its holders.
135      */
136     private final boolean mSystemOnly;
137 
138     /**
139      * The required components for an application to qualify for this role.
140      */
141     @NonNull
142     private final List<RequiredComponent> mRequiredComponents;
143 
144     /**
145      * The permissions to be granted by this role.
146      */
147     @NonNull
148     private final List<String> mPermissions;
149 
150     /**
151      * The app ops to be set to allowed by this role.
152      */
153     @NonNull
154     private final List<AppOp> mAppOps;
155 
156     /**
157      * The set of preferred {@code Activity} configurations to be configured by this role.
158      */
159     @NonNull
160     private final List<PreferredActivity> mPreferredActivities;
161 
Role(@onNull String name, @Nullable RoleBehavior behavior, @StringRes int descriptionResource, boolean exclusive, @StringRes int labelResource, @StringRes int requestDescriptionResource, @StringRes int requestTitleResource, boolean requestable, @StringRes int shortLabelResource, boolean showNone, boolean systemOnly, @NonNull List<RequiredComponent> requiredComponents, @NonNull List<String> permissions, @NonNull List<AppOp> appOps, @NonNull List<PreferredActivity> preferredActivities)162     public Role(@NonNull String name, @Nullable RoleBehavior behavior,
163             @StringRes int descriptionResource, boolean exclusive, @StringRes int labelResource,
164             @StringRes int requestDescriptionResource, @StringRes int requestTitleResource,
165             boolean requestable, @StringRes int shortLabelResource, boolean showNone,
166             boolean systemOnly, @NonNull List<RequiredComponent> requiredComponents,
167             @NonNull List<String> permissions, @NonNull List<AppOp> appOps,
168             @NonNull List<PreferredActivity> preferredActivities) {
169         mName = name;
170         mBehavior = behavior;
171         mDescriptionResource = descriptionResource;
172         mExclusive = exclusive;
173         mLabelResource = labelResource;
174         mRequestDescriptionResource = requestDescriptionResource;
175         mRequestTitleResource = requestTitleResource;
176         mRequestable = requestable;
177         mShortLabelResource = shortLabelResource;
178         mShowNone = showNone;
179         mSystemOnly = systemOnly;
180         mRequiredComponents = requiredComponents;
181         mPermissions = permissions;
182         mAppOps = appOps;
183         mPreferredActivities = preferredActivities;
184     }
185 
186     @NonNull
getName()187     public String getName() {
188         return mName;
189     }
190 
191     @Nullable
getBehavior()192     public RoleBehavior getBehavior() {
193         return mBehavior;
194     }
195 
196     @StringRes
getDescriptionResource()197     public int getDescriptionResource() {
198         return mDescriptionResource;
199     }
200 
isExclusive()201     public boolean isExclusive() {
202         return mExclusive;
203     }
204 
205     @StringRes
getLabelResource()206     public int getLabelResource() {
207         return mLabelResource;
208     }
209 
210     @StringRes
getRequestDescriptionResource()211     public int getRequestDescriptionResource() {
212         return mRequestDescriptionResource;
213     }
214 
215     @StringRes
getRequestTitleResource()216     public int getRequestTitleResource() {
217         return mRequestTitleResource;
218     }
219 
isRequestable()220     public boolean isRequestable() {
221         return mRequestable;
222     }
223 
224     @StringRes
getShortLabelResource()225     public int getShortLabelResource() {
226         return mShortLabelResource;
227     }
228 
229     /**
230      * @see #mShowNone
231      */
shouldShowNone()232     public boolean shouldShowNone() {
233         return mShowNone;
234     }
235 
236     @NonNull
getRequiredComponents()237     public List<RequiredComponent> getRequiredComponents() {
238         return mRequiredComponents;
239     }
240 
241     @NonNull
getPermissions()242     public List<String> getPermissions() {
243         return mPermissions;
244     }
245 
246     @NonNull
getAppOps()247     public List<AppOp> getAppOps() {
248         return mAppOps;
249     }
250 
251     @NonNull
getPreferredActivities()252     public List<PreferredActivity> getPreferredActivities() {
253         return mPreferredActivities;
254     }
255 
256     /**
257      * Callback when this role is added to the system for the first time.
258      *
259      * @param context the {@code Context} to retrieve system services
260      */
onRoleAdded(@onNull Context context)261     public void onRoleAdded(@NonNull Context context) {
262         if (mBehavior != null) {
263             mBehavior.onRoleAdded(this, context);
264         }
265     }
266 
267     /**
268      * Check whether this role is available.
269      *
270      * @param user the user to check for
271      * @param context the {@code Context} to retrieve system services
272      *
273      * @return whether this role is available.
274      */
isAvailableAsUser(@onNull UserHandle user, @NonNull Context context)275     public boolean isAvailableAsUser(@NonNull UserHandle user, @NonNull Context context) {
276         if (mBehavior != null) {
277             return mBehavior.isAvailableAsUser(this, user, context);
278         }
279         return true;
280     }
281 
282     /**
283      * Check whether this role is available, for current user.
284      *
285      * @param context the {@code Context} to retrieve system services
286      *
287      * @return whether this role is available.
288      */
isAvailable(@onNull Context context)289     public boolean isAvailable(@NonNull Context context) {
290         return isAvailableAsUser(Process.myUserHandle(), context);
291     }
292 
293     /**
294      * Get the default holders of this role, which will be added when the role is added for the
295      * first time.
296      *
297      * @param context the {@code Context} to retrieve system services
298      *
299      * @return the list of package names of the default holders
300      */
301     @NonNull
getDefaultHolders(@onNull Context context)302     public List<String> getDefaultHolders(@NonNull Context context) {
303         if (mBehavior != null) {
304             return mBehavior.getDefaultHolders(this, context);
305         }
306         return Collections.emptyList();
307     }
308 
309     /**
310      * Get the fallback holder of this role, which will be added whenever there are no role holders.
311      * <p>
312      * Should return {@code null} if this role {@link #mShowNone shows a "None" item}.
313      *
314      * @param context the {@code Context} to retrieve system services
315      *
316      * @return the package name of the fallback holder, or {@code null} if none
317      */
318     @Nullable
getFallbackHolder(@onNull Context context)319     public String getFallbackHolder(@NonNull Context context) {
320         if (mBehavior != null && !isNoneHolderSelected(context)) {
321             return mBehavior.getFallbackHolder(this, context);
322         }
323         return null;
324     }
325 
326     /**
327      * Check whether this role should be visible to user.
328      *
329      * @param user the user to check for
330      * @param context the {@code Context} to retrieve system services
331      *
332      * @return whether this role should be visible to user
333      */
isVisibleAsUser(@onNull UserHandle user, @NonNull Context context)334     public boolean isVisibleAsUser(@NonNull UserHandle user, @NonNull Context context) {
335         if (mBehavior != null) {
336             return mBehavior.isVisibleAsUser(this, user, context);
337         }
338         return true;
339     }
340 
341     /**
342      * Check whether this role should be visible to user, for current user.
343      *
344      * @param context the {@code Context} to retrieve system services
345      *
346      * @return whether this role should be visible to user.
347      */
isVisible(@onNull Context context)348     public boolean isVisible(@NonNull Context context) {
349         return isVisibleAsUser(Process.myUserHandle(), context);
350     }
351 
352     /**
353      * Get the {@link Intent} to manage this role, or {@code null} to use the default UI.
354      *
355      * @param user the user to manage this role for
356      * @param context the {@code Context} to retrieve system services
357      *
358      * @return the {@link Intent} to manage this role, or {@code null} to use the default UI.
359      */
360     @Nullable
getManageIntentAsUser(@onNull UserHandle user, @NonNull Context context)361     public Intent getManageIntentAsUser(@NonNull UserHandle user, @NonNull Context context) {
362         if (mBehavior != null) {
363             return mBehavior.getManageIntentAsUser(this, user, context);
364         }
365         return null;
366     }
367 
368     /**
369      * Prepare a {@link Preference} for this role.
370      *
371      * @param preference the {@link Preference} for this role
372      * @param user the user for this role
373      * @param context the {@code Context} to retrieve system services
374      */
preparePreferenceAsUser(@onNull TwoTargetPreference preference, @NonNull UserHandle user, @NonNull Context context)375     public void preparePreferenceAsUser(@NonNull TwoTargetPreference preference,
376             @NonNull UserHandle user, @NonNull Context context) {
377         if (mBehavior != null) {
378             mBehavior.preparePreferenceAsUser(this, preference, user, context);
379         }
380     }
381 
382     /**
383      * Check whether a qualifying application should be visible to user.
384      *
385      * @param applicationInfo the {@link ApplicationInfo} for the application
386      * @param user the user for the application
387      * @param context the {@code Context} to retrieve system services
388      *
389      * @return whether the qualifying application should be visible to user
390      */
isApplicationVisibleAsUser(@onNull ApplicationInfo applicationInfo, @NonNull UserHandle user, @NonNull Context context)391     public boolean isApplicationVisibleAsUser(@NonNull ApplicationInfo applicationInfo,
392             @NonNull UserHandle user, @NonNull Context context) {
393         if (mBehavior != null) {
394             return mBehavior.isApplicationVisibleAsUser(this, applicationInfo, user, context);
395         }
396         return true;
397     }
398 
399     /**
400      * Prepare a {@link Preference} for an application.
401      *
402      * @param preference the {@link Preference} for the application
403      * @param applicationInfo the {@link ApplicationInfo} for the application
404      * @param user the user for the application
405      * @param context the {@code Context} to retrieve system services
406      */
prepareApplicationPreferenceAsUser(@onNull Preference preference, @NonNull ApplicationInfo applicationInfo, @NonNull UserHandle user, @NonNull Context context)407     public void prepareApplicationPreferenceAsUser(@NonNull Preference preference,
408             @NonNull ApplicationInfo applicationInfo, @NonNull UserHandle user,
409             @NonNull Context context) {
410         if (mBehavior != null) {
411             mBehavior.prepareApplicationPreferenceAsUser(this, preference, applicationInfo, user,
412                     context);
413         }
414     }
415 
416     /**
417      * Get the confirmation message for adding an application as a holder of this role.
418      *
419      * @param packageName the package name of the application to get confirmation message for
420      * @param context the {@code Context} to retrieve system services
421      *
422      * @return the confirmation message, or {@code null} if no confirmation is needed
423      */
424     @Nullable
getConfirmationMessage(@onNull String packageName, @NonNull Context context)425     public CharSequence getConfirmationMessage(@NonNull String packageName,
426             @NonNull Context context) {
427         if (mBehavior != null) {
428             return mBehavior.getConfirmationMessage(this, packageName, context);
429         }
430         return null;
431     }
432 
433     /**
434      * Check whether a package is qualified for this role, i.e. whether it contains all the required
435      * components (plus meeting some other general restrictions).
436      *
437      * @param packageName the package name to check for
438      * @param context the {@code Context} to retrieve system services
439      *
440      * @return whether the package is qualified for a role
441      */
isPackageQualified(@onNull String packageName, @NonNull Context context)442     public boolean isPackageQualified(@NonNull String packageName, @NonNull Context context) {
443         if (!isPackageMinimallyQualifiedAsUser(packageName, Process.myUserHandle(), context)) {
444             return false;
445         }
446 
447         if (mBehavior != null) {
448             Boolean isPackageQualified = mBehavior.isPackageQualified(this, packageName, context);
449             if (isPackageQualified != null) {
450                 return isPackageQualified;
451             }
452         }
453 
454         int requiredComponentsSize = mRequiredComponents.size();
455         for (int i = 0; i < requiredComponentsSize; i++) {
456             RequiredComponent requiredComponent = mRequiredComponents.get(i);
457             if (requiredComponent.getQualifyingComponentForPackage(packageName, context) == null) {
458                 Log.w(LOG_TAG, packageName + " not qualified for " + mName
459                         + " due to missing " + requiredComponent);
460                 return false;
461             }
462         }
463 
464         return true;
465     }
466 
467     /**
468      * Get the list of packages that are qualified for this role, i.e. packages containing all the
469      * required components (plus meeting some other general restrictions).
470      *
471      * @param user the user to get the qualifying packages.
472      * @param context the {@code Context} to retrieve system services
473      *
474      * @return the list of packages that are qualified for this role
475      */
476     @NonNull
getQualifyingPackagesAsUser(@onNull UserHandle user, @NonNull Context context)477     public List<String> getQualifyingPackagesAsUser(@NonNull UserHandle user,
478             @NonNull Context context) {
479         List<String> qualifyingPackages = null;
480 
481         if (mBehavior != null) {
482             qualifyingPackages = mBehavior.getQualifyingPackagesAsUser(this, user, context);
483         }
484 
485         if (qualifyingPackages == null) {
486             ArrayMap<String, Integer> packageComponentCountMap = new ArrayMap<>();
487             int requiredComponentsSize = mRequiredComponents.size();
488             for (int requiredComponentsIndex = 0; requiredComponentsIndex < requiredComponentsSize;
489                     requiredComponentsIndex++) {
490                 RequiredComponent requiredComponent = mRequiredComponents.get(
491                         requiredComponentsIndex);
492 
493                 // This returns at most one component per package.
494                 List<ComponentName> qualifyingComponents =
495                         requiredComponent.getQualifyingComponentsAsUser(user, context);
496                 int qualifyingComponentsSize = qualifyingComponents.size();
497                 for (int qualifyingComponentsIndex = 0;
498                         qualifyingComponentsIndex < qualifyingComponentsSize;
499                         ++qualifyingComponentsIndex) {
500                     ComponentName componentName = qualifyingComponents.get(
501                             qualifyingComponentsIndex);
502 
503                     String packageName = componentName.getPackageName();
504                     Integer componentCount = packageComponentCountMap.get(packageName);
505                     packageComponentCountMap.put(packageName, componentCount == null ? 1
506                             : componentCount + 1);
507                 }
508             }
509 
510             qualifyingPackages = new ArrayList<>();
511             int packageComponentCountMapSize = packageComponentCountMap.size();
512             for (int i = 0; i < packageComponentCountMapSize; i++) {
513                 int componentCount = packageComponentCountMap.valueAt(i);
514 
515                 if (componentCount != requiredComponentsSize) {
516                     continue;
517                 }
518                 String packageName = packageComponentCountMap.keyAt(i);
519                 qualifyingPackages.add(packageName);
520             }
521         }
522 
523         int qualifyingPackagesSize = qualifyingPackages.size();
524         for (int i = 0; i < qualifyingPackagesSize; ) {
525             String packageName = qualifyingPackages.get(i);
526 
527             if (!isPackageMinimallyQualifiedAsUser(packageName, user, context)) {
528                 qualifyingPackages.remove(i);
529                 qualifyingPackagesSize--;
530             } else {
531                 i++;
532             }
533         }
534 
535         return qualifyingPackages;
536     }
537 
isPackageMinimallyQualifiedAsUser( @onNull String packageName, @NonNull UserHandle user, @NonNull Context context)538     private boolean isPackageMinimallyQualifiedAsUser(
539             @NonNull String packageName, @NonNull UserHandle user, @NonNull Context context) {
540         if (Objects.equals(packageName, PACKAGE_NAME_ANDROID_SYSTEM)) {
541             return false;
542         }
543 
544         ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName, user,
545                 context);
546         if (applicationInfo == null) {
547             Log.w(LOG_TAG, "Cannot get ApplicationInfo for package: " + packageName + ", user: "
548                     + user.getIdentifier());
549             return false;
550         }
551 
552         if (mSystemOnly && (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
553             return false;
554         }
555 
556         if (!applicationInfo.enabled) {
557             return false;
558         }
559 
560         if (applicationInfo.isInstantApp()) {
561             return false;
562         }
563 
564         PackageManager userPackageManager = UserUtils.getUserContext(context, user)
565                 .getPackageManager();
566         if (!userPackageManager.getDeclaredSharedLibraries(packageName, 0).isEmpty()) {
567             return false;
568         }
569 
570         return true;
571     }
572 
573     /**
574      * Grant this role to an application.
575      *
576      * @param packageName the package name of the application to be granted this role to
577      * @param dontKillApp whether this application should not be killed despite changes
578      * @param overrideUserSetAndFixedPermissions whether to override user set and fixed flags on
579      *                                           permissions
580      * @param context the {@code Context} to retrieve system services
581      */
grant(@onNull String packageName, boolean dontKillApp, boolean overrideUserSetAndFixedPermissions, @NonNull Context context)582     public void grant(@NonNull String packageName, boolean dontKillApp,
583             boolean overrideUserSetAndFixedPermissions, @NonNull Context context) {
584         boolean permissionOrAppOpChanged = Permissions.grant(packageName, mPermissions, true,
585                 overrideUserSetAndFixedPermissions, true, false, false, context);
586 
587         int appOpsSize = mAppOps.size();
588         for (int i = 0; i < appOpsSize; i++) {
589             AppOp appOp = mAppOps.get(i);
590             appOp.grant(packageName, context);
591         }
592 
593         int preferredActivitiesSize = mPreferredActivities.size();
594         for (int i = 0; i < preferredActivitiesSize; i++) {
595             PreferredActivity preferredActivity = mPreferredActivities.get(i);
596             preferredActivity.configure(packageName, context);
597         }
598 
599         if (mBehavior != null) {
600             mBehavior.grant(this, packageName, context);
601         }
602 
603         if (!dontKillApp && permissionOrAppOpChanged && !Permissions.isRuntimePermissionsSupported(
604                 packageName, context)) {
605             killApp(packageName, context);
606         }
607     }
608 
609     /**
610      * Revoke this role from an application.
611      *
612      * @param packageName the package name of the application to be granted this role to
613      * @param dontKillApp whether this application should not be killed despite changes
614      * @param overrideSystemFixedPermissions whether system-fixed permissions can be revoked
615      * @param context the {@code Context} to retrieve system services
616      */
revoke(@onNull String packageName, boolean dontKillApp, boolean overrideSystemFixedPermissions, @NonNull Context context)617     public void revoke(@NonNull String packageName, boolean dontKillApp,
618             boolean overrideSystemFixedPermissions, @NonNull Context context) {
619         RoleManager roleManager = context.getSystemService(RoleManager.class);
620         List<String> otherRoleNames = roleManager.getHeldRolesFromController(packageName);
621         otherRoleNames.remove(mName);
622 
623         List<String> permissionsToRevoke = new ArrayList<>(mPermissions);
624         ArrayMap<String, Role> roles = Roles.get(context);
625         int otherRoleNamesSize = otherRoleNames.size();
626         for (int i = 0; i < otherRoleNamesSize; i++) {
627             String roleName = otherRoleNames.get(i);
628             Role role = roles.get(roleName);
629             permissionsToRevoke.removeAll(role.getPermissions());
630         }
631         boolean permissionOrAppOpChanged = Permissions.revoke(packageName, permissionsToRevoke,
632                 true, false, overrideSystemFixedPermissions, context);
633 
634         List<AppOp> appOpsToRevoke = new ArrayList<>(mAppOps);
635         for (int i = 0; i < otherRoleNamesSize; i++) {
636             String roleName = otherRoleNames.get(i);
637             Role role = roles.get(roleName);
638             appOpsToRevoke.removeAll(role.getAppOps());
639         }
640         int appOpsSize = appOpsToRevoke.size();
641         for (int i = 0; i < appOpsSize; i++) {
642             AppOp appOp = appOpsToRevoke.get(i);
643             appOp.revoke(packageName, context);
644         }
645 
646         // TODO: Revoke preferred activities? But this is unnecessary for most roles using it as
647         //  they have fallback holders. Moreover, clearing the preferred activity might result in
648         //  other system components listening to preferred activity change get notified for the
649         //  wrong thing when we are removing a exclusive role holder for adding another.
650 
651         if (mBehavior != null) {
652             mBehavior.revoke(this, packageName, context);
653         }
654 
655         if (!dontKillApp && permissionOrAppOpChanged) {
656             killApp(packageName, context);
657         }
658     }
659 
killApp(@onNull String packageName, @NonNull Context context)660     private void killApp(@NonNull String packageName, @NonNull Context context) {
661         if (DEBUG) {
662             Log.i(LOG_TAG, "Killing " + packageName + " due to "
663                     + Thread.currentThread().getStackTrace()[3].getMethodName()
664                     + "(" + mName + ")");
665         }
666         ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(packageName, context);
667         if (applicationInfo == null) {
668             Log.w(LOG_TAG, "Cannot get ApplicationInfo for package: " + packageName);
669             return;
670         }
671         ActivityManager activityManager = context.getSystemService(ActivityManager.class);
672         activityManager.killUid(applicationInfo.uid, "Permission or app op changed");
673     }
674 
675     /**
676      * Check whether the "none" role holder is selected.
677      *
678      * @param context the {@code Context} to retrieve system services
679      *
680      * @return whether the "none" role holder is selected
681      */
isNoneHolderSelected(@onNull Context context)682     private boolean isNoneHolderSelected(@NonNull Context context) {
683         return Utils.getDeviceProtectedSharedPreferences(context).getBoolean(
684                 Constants.IS_NONE_ROLE_HOLDER_SELECTED_KEY + mName, false);
685     }
686 
687     /**
688      * Callback when a role holder (other than "none") was added.
689      *
690      * @param packageName the package name of the role holder
691      * @param user the user for the role
692      * @param context the {@code Context} to retrieve system services
693      */
onHolderAddedAsUser(@onNull String packageName, @NonNull UserHandle user, @NonNull Context context)694     public void onHolderAddedAsUser(@NonNull String packageName, @NonNull UserHandle user,
695             @NonNull Context context) {
696         Utils.getDeviceProtectedSharedPreferences(UserUtils.getUserContext(context, user)).edit()
697                 .remove(Constants.IS_NONE_ROLE_HOLDER_SELECTED_KEY + mName)
698                 .apply();
699     }
700 
701     /**
702      * Callback when a role holder (other than "none") was selected in the UI and added
703      * successfully.
704      *
705      * @param packageName the package name of the role holder
706      * @param user the user for the role
707      * @param context the {@code Context} to retrieve system services
708      */
onHolderSelectedAsUser(@onNull String packageName, @NonNull UserHandle user, @NonNull Context context)709     public void onHolderSelectedAsUser(@NonNull String packageName, @NonNull UserHandle user,
710             @NonNull Context context) {
711         if (mBehavior != null) {
712             mBehavior.onHolderSelectedAsUser(this, packageName, user, context);
713         }
714     }
715 
716     /**
717      * Callback when a role holder changed.
718      *
719      * @param user the user for the role
720      * @param context the {@code Context} to retrieve system services
721      */
onHolderChangedAsUser(@onNull UserHandle user, @NonNull Context context)722     public void onHolderChangedAsUser(@NonNull UserHandle user,
723             @NonNull Context context) {
724         if (mBehavior != null) {
725             mBehavior.onHolderChangedAsUser(this, user, context);
726         }
727     }
728 
729     /**
730      * Callback when the "none" role holder was selected in the UI.
731      *
732      * @param user the user for the role
733      * @param context the {@code Context} to retrieve system services
734      */
onNoneHolderSelectedAsUser(@onNull UserHandle user, @NonNull Context context)735     public void onNoneHolderSelectedAsUser(@NonNull UserHandle user, @NonNull Context context) {
736         Utils.getDeviceProtectedSharedPreferences(UserUtils.getUserContext(context, user)).edit()
737                 .putBoolean(Constants.IS_NONE_ROLE_HOLDER_SELECTED_KEY + mName, true)
738                 .apply();
739     }
740 
741     @Override
toString()742     public String toString() {
743         return "Role{"
744                 + "mName='" + mName + '\''
745                 + ", mBehavior=" + mBehavior
746                 + ", mExclusive=" + mExclusive
747                 + ", mLabelResource=" + mLabelResource
748                 + ", mShowNone=" + mShowNone
749                 + ", mSystemOnly=" + mSystemOnly
750                 + ", mRequiredComponents=" + mRequiredComponents
751                 + ", mPermissions=" + mPermissions
752                 + ", mAppOps=" + mAppOps
753                 + ", mPreferredActivities=" + mPreferredActivities
754                 + '}';
755     }
756 
757     @Override
equals(Object object)758     public boolean equals(Object object) {
759         if (this == object) {
760             return true;
761         }
762         if (object == null || getClass() != object.getClass()) {
763             return false;
764         }
765         Role that = (Role) object;
766         return mExclusive == that.mExclusive
767                 && mLabelResource == that.mLabelResource
768                 && mShowNone == that.mShowNone
769                 && mSystemOnly == that.mSystemOnly
770                 && mName.equals(that.mName)
771                 && Objects.equals(mBehavior, that.mBehavior)
772                 && mRequiredComponents.equals(that.mRequiredComponents)
773                 && mPermissions.equals(that.mPermissions)
774                 && mAppOps.equals(that.mAppOps)
775                 && mPreferredActivities.equals(that.mPreferredActivities);
776     }
777 
778     @Override
hashCode()779     public int hashCode() {
780         return Objects.hash(mName, mBehavior, mExclusive, mLabelResource, mShowNone, mSystemOnly,
781                 mRequiredComponents, mPermissions, mAppOps, mPreferredActivities);
782     }
783 }
784