• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.permissioncontroller.role.ui;
18 
19 import static com.android.permissioncontroller.PermissionControllerStatsLog.ROLE_SETTINGS_FRAGMENT_ACTION_REPORTED;
20 
21 import android.app.Activity;
22 import android.app.role.RoleManager;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.ApplicationInfo;
26 import android.graphics.drawable.Drawable;
27 import android.os.Bundle;
28 import android.os.Process;
29 import android.os.UserHandle;
30 import android.util.ArrayMap;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 import androidx.appcompat.content.res.AppCompatResources;
35 import androidx.fragment.app.Fragment;
36 import androidx.lifecycle.ViewModelProvider;
37 import androidx.preference.Preference;
38 import androidx.preference.PreferenceCategory;
39 import androidx.preference.PreferenceFragmentCompat;
40 import androidx.preference.PreferenceGroup;
41 import androidx.preference.PreferenceManager;
42 import androidx.preference.PreferenceScreen;
43 import androidx.preference.TwoStatePreference;
44 
45 import com.android.modules.utils.build.SdkLevel;
46 import com.android.permission.flags.Flags;
47 import com.android.permissioncontroller.PermissionControllerStatsLog;
48 import com.android.permissioncontroller.R;
49 import com.android.permissioncontroller.permission.utils.Utils;
50 import com.android.permissioncontroller.role.utils.PackageUtils;
51 import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
52 import com.android.permissioncontroller.role.utils.SettingsCompat;
53 import com.android.role.controller.model.Role;
54 import com.android.role.controller.model.Roles;
55 import com.android.settingslib.utils.applications.AppUtils;
56 
57 import java.util.List;
58 import java.util.Objects;
59 
60 /**
61  * Child fragment for a default app.
62  * <p>
63  * Must be added as a child fragment and its parent fragment must be a
64  * {@link PreferenceFragmentCompat} that implements {@link Parent}.
65  *
66  * @param <PF> type of the parent fragment
67  */
68 public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat
69         & DefaultAppChildFragment.Parent> extends Fragment
70         implements DefaultAppConfirmationDialogFragment.Listener,
71         Preference.OnPreferenceClickListener {
72 
73     private static final String PREFERENCE_KEY_RECOMMENDED_CATEGORY =
74             DefaultAppChildFragment.class.getName() + ".preference.RECOMMENDED_CATEGORY";
75     private static final String PREFERENCE_KEY_OTHERS_CATEGORY =
76             DefaultAppChildFragment.class.getName() + ".preference.OTHERS_CATEGORY";
77     private static final String PREFERENCE_KEY_NONE = DefaultAppChildFragment.class.getName()
78             + ".preference.NONE";
79     private static final String PREFERENCE_KEY_DESCRIPTION = DefaultAppChildFragment.class.getName()
80             + ".preference.DESCRIPTION";
81     private static final String PREFERENCE_KEY_OTHER_NFC_SERVICES =
82             DefaultAppChildFragment.class.getName() + ".preference.OTHER_NFC_SERVICES";
83     private static final String PREFERENCE_EXTRA_PACKAGE_NAME =
84             DefaultAppChildFragment.class.getName() + ".extra.PACKAGE_NAME";
85     private static final String PREFERENCE_EXTRA_UID = DefaultAppChildFragment.class.getName()
86             + ".extra.UID";
87 
88     @NonNull
89     private String mRoleName;
90     @NonNull
91     private UserHandle mUser;
92 
93     @NonNull
94     private Role mRole;
95 
96     @NonNull
97     private DefaultAppViewModel mViewModel;
98 
99     /**
100      * Create a new instance of this fragment.
101      *
102      * @param roleName the name of the role for the default app
103      * @param user the user for the default app
104      *
105      * @return a new instance of this fragment
106      */
107     @NonNull
newInstance(@onNull String roleName, @NonNull UserHandle user)108     public static DefaultAppChildFragment newInstance(@NonNull String roleName,
109             @NonNull UserHandle user) {
110         DefaultAppChildFragment fragment = new DefaultAppChildFragment();
111         Bundle arguments = new Bundle();
112         arguments.putString(Intent.EXTRA_ROLE_NAME, roleName);
113         arguments.putParcelable(Intent.EXTRA_USER, user);
114         fragment.setArguments(arguments);
115         return fragment;
116     }
117 
118     @Override
onCreate(@ullable Bundle savedInstanceState)119     public void onCreate(@Nullable Bundle savedInstanceState) {
120         super.onCreate(savedInstanceState);
121 
122         Bundle arguments = getArguments();
123         mRoleName = arguments.getString(Intent.EXTRA_ROLE_NAME);
124         mUser = arguments.getParcelable(Intent.EXTRA_USER);
125     }
126 
127     @Override
onActivityCreated(@ullable Bundle savedInstanceState)128     public void onActivityCreated(@Nullable Bundle savedInstanceState) {
129         super.onActivityCreated(savedInstanceState);
130 
131         PF preferenceFragment = requirePreferenceFragment();
132         Activity activity = requireActivity();
133         mRole = Roles.get(activity).get(mRoleName);
134         preferenceFragment.setTitle(getString(mRole.getLabelResource()));
135 
136         ViewModelProvider.Factory viewModelFactory = new DefaultAppViewModel.Factory(mRole, mUser,
137                 activity.getApplication());
138         mViewModel = new ViewModelProvider(this, viewModelFactory).get(DefaultAppViewModel.class);
139         mViewModel.getRecommendedLiveData().observe(this,
140                 applicationItems -> onApplicationListChanged());
141         mViewModel.getLiveData().observe(this, applicationItems -> onApplicationListChanged());
142         mViewModel.getManageRoleHolderStateLiveData().observe(this,
143                 this::onManageRoleHolderStateChanged);
144     }
145 
onApplicationListChanged()146     private void onApplicationListChanged() {
147         List<RoleApplicationItem> recommendedApplicationItems =
148                 mViewModel.getRecommendedLiveData().getValue();
149         if (recommendedApplicationItems == null) {
150             return;
151         }
152         List<RoleApplicationItem> otherApplicationItems = mViewModel.getLiveData().getValue();
153         if (otherApplicationItems == null) {
154             return;
155         }
156 
157         PF preferenceFragment = requirePreferenceFragment();
158         PreferenceManager preferenceManager = preferenceFragment.getPreferenceManager();
159         Context context = preferenceManager.getContext();
160 
161         PreferenceScreen preferenceScreen = preferenceFragment.getPreferenceScreen();
162         PreferenceCategory oldRecommendedPreferenceCategory = null;
163         PreferenceCategory oldOthersPreferenceCategory = null;
164         ArrayMap<String, Preference> oldPreferences = new ArrayMap<>();
165         if (preferenceScreen == null) {
166             preferenceScreen = preferenceManager.createPreferenceScreen(context);
167             preferenceFragment.setPreferenceScreen(preferenceScreen);
168         } else {
169             if (Flags.defaultAppsRecommendationEnabled()) {
170                 oldRecommendedPreferenceCategory =
171                         preferenceScreen.findPreference(PREFERENCE_KEY_RECOMMENDED_CATEGORY);
172                 clearPreferenceCategory(oldRecommendedPreferenceCategory, oldPreferences);
173                 oldOthersPreferenceCategory =
174                         preferenceScreen.findPreference(PREFERENCE_KEY_OTHERS_CATEGORY);
175                 clearPreferenceCategory(oldOthersPreferenceCategory, oldPreferences);
176             }
177             clearPreferences(preferenceScreen, oldPreferences);
178         }
179 
180         if (Flags.defaultAppsRecommendationEnabled() && !recommendedApplicationItems.isEmpty()) {
181             addApplicationPreferenceCategory(oldRecommendedPreferenceCategory,
182                     PREFERENCE_KEY_RECOMMENDED_CATEGORY,
183                     getString(R.string.default_app_recommended), preferenceScreen, false, false,
184                     recommendedApplicationItems, oldPreferences, context);
185             if (mRole.shouldShowNone() || !otherApplicationItems.isEmpty()) {
186                 boolean noneChecked = !(hasHolderApplication(recommendedApplicationItems)
187                         || hasHolderApplication(otherApplicationItems));
188                 addApplicationPreferenceCategory(oldOthersPreferenceCategory,
189                         PREFERENCE_KEY_OTHERS_CATEGORY, getString(R.string.default_app_others),
190                         preferenceScreen, true, noneChecked, otherApplicationItems, oldPreferences,
191                         context);
192             }
193         } else {
194             boolean noneChecked = !hasHolderApplication(otherApplicationItems);
195             addNonePreferenceIfNeeded(preferenceScreen, noneChecked, oldPreferences, context);
196             addApplicationPreferences(preferenceScreen, otherApplicationItems, oldPreferences,
197                     context);
198         }
199 
200         addNonPaymentNfcServicesPreference(preferenceScreen, oldPreferences, context);
201         addDescriptionPreference(preferenceScreen, oldPreferences);
202 
203         preferenceFragment.onPreferenceScreenChanged();
204     }
205 
clearPreferenceCategory(@ullable PreferenceCategory preferenceCategory, @NonNull ArrayMap<String, Preference> oldPreferences)206     private static void clearPreferenceCategory(@Nullable PreferenceCategory preferenceCategory,
207             @NonNull ArrayMap<String, Preference> oldPreferences) {
208         if (preferenceCategory == null) {
209             return;
210         }
211         clearPreferences(preferenceCategory, oldPreferences);
212         preferenceCategory.getParent().removePreference(preferenceCategory);
213         preferenceCategory.setOrder(Preference.DEFAULT_ORDER);
214     }
215 
clearPreferences(@onNull PreferenceGroup preferenceGroup, @NonNull ArrayMap<String, Preference> oldPreferences)216     private static void clearPreferences(@NonNull PreferenceGroup preferenceGroup,
217             @NonNull ArrayMap<String, Preference> oldPreferences) {
218         for (int i = preferenceGroup.getPreferenceCount() - 1; i >= 0; --i) {
219             Preference preference = preferenceGroup.getPreference(i);
220 
221             preferenceGroup.removePreference(preference);
222             preference.setOrder(Preference.DEFAULT_ORDER);
223             oldPreferences.put(preference.getKey(), preference);
224         }
225     }
226 
addApplicationPreferenceCategory( @ullable PreferenceCategory oldPreferenceCategory, @NonNull String key, @Nullable String title, @NonNull PreferenceScreen preferenceScreen, boolean addNonePreferenceIfNeeded, boolean noneChecked, @NonNull List<RoleApplicationItem> applicationItems, @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context)227     private void addApplicationPreferenceCategory(
228             @Nullable PreferenceCategory oldPreferenceCategory, @NonNull String key,
229             @Nullable String title, @NonNull PreferenceScreen preferenceScreen,
230             boolean addNonePreferenceIfNeeded, boolean noneChecked,
231             @NonNull List<RoleApplicationItem> applicationItems,
232             @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context) {
233         PreferenceCategory preferenceCategory = oldPreferenceCategory;
234         if (preferenceCategory == null) {
235             preferenceCategory = new PreferenceCategory(context);
236             preferenceCategory.setKey(key);
237             preferenceCategory.setTitle(title);
238         }
239         preferenceScreen.addPreference(preferenceCategory);
240         if (addNonePreferenceIfNeeded) {
241             addNonePreferenceIfNeeded(preferenceCategory, noneChecked, oldPreferences, context);
242         }
243         addApplicationPreferences(preferenceCategory, applicationItems, oldPreferences, context);
244     }
245 
hasHolderApplication( @onNull List<RoleApplicationItem> applicationItems)246     private static boolean hasHolderApplication(
247             @NonNull List<RoleApplicationItem> applicationItems) {
248         int applicationItemsSize = applicationItems.size();
249         for (int i = 0; i < applicationItemsSize; i++) {
250             RoleApplicationItem applicationItem = applicationItems.get(i);
251             if (applicationItem.isHolderApplication()) {
252                 return true;
253             }
254         }
255         return false;
256     }
257 
addNonePreferenceIfNeeded(@onNull PreferenceGroup preferenceGroup, boolean checked, @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context)258     private void addNonePreferenceIfNeeded(@NonNull PreferenceGroup preferenceGroup,
259             boolean checked, @NonNull ArrayMap<String, Preference> oldPreferences,
260             @NonNull Context context) {
261         if (!mRole.shouldShowNone()) {
262             return;
263         }
264 
265         Drawable icon = AppCompatResources.getDrawable(context, R.drawable.ic_remove_circle);
266         String title = getString(R.string.default_app_none);
267         addApplicationPreference(preferenceGroup, PREFERENCE_KEY_NONE, icon, title, checked, null,
268                 oldPreferences, context);
269     }
270 
addApplicationPreferences(@onNull PreferenceGroup preferenceGroup, @NonNull List<RoleApplicationItem> applicationItems, @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context)271     private void addApplicationPreferences(@NonNull PreferenceGroup preferenceGroup,
272             @NonNull List<RoleApplicationItem> applicationItems,
273             @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context) {
274         int applicationItemsSize = applicationItems.size();
275         for (int i = 0; i < applicationItemsSize; i++) {
276             RoleApplicationItem applicationItem = applicationItems.get(i);
277             ApplicationInfo applicationInfo = applicationItem.getApplicationInfo();
278             int userId = UserHandle.getUserHandleForUid(applicationInfo.uid).getIdentifier();
279             String key = applicationInfo.packageName + "@" + userId;
280             Drawable icon = Utils.getBadgedIcon(context, applicationInfo);
281             String title = Utils.getFullAppLabel(applicationInfo, context);
282             boolean isHolderApplication = applicationItem.isHolderApplication();
283 
284             addApplicationPreference(preferenceGroup, key, icon, title, isHolderApplication,
285                     applicationInfo, oldPreferences, context);
286         }
287     }
288 
addApplicationPreference(@onNull PreferenceGroup preferenceGroup, @NonNull String key, @NonNull Drawable icon, @NonNull CharSequence title, boolean checked, @Nullable ApplicationInfo applicationInfo, @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context)289     private void addApplicationPreference(@NonNull PreferenceGroup preferenceGroup,
290             @NonNull String key, @NonNull Drawable icon, @NonNull CharSequence title,
291             boolean checked, @Nullable ApplicationInfo applicationInfo,
292             @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context) {
293         RoleApplicationPreference roleApplicationPreference =
294                 (RoleApplicationPreference) oldPreferences.get(key);
295         TwoStatePreference preference;
296         if (roleApplicationPreference == null) {
297             roleApplicationPreference = requirePreferenceFragment().createApplicationPreference();
298             preference = roleApplicationPreference.asTwoStatePreference();
299             preference.setKey(key);
300             preference.setIcon(icon);
301             preference.setTitle(title);
302             preference.setPersistent(false);
303             preference.setOnPreferenceChangeListener((preference2, newValue) -> false);
304             preference.setOnPreferenceClickListener(this);
305             // In the cases we need this (see #onPreferenceClick()), this should never be null.
306             // This method (addPreference) is used for both legitimate apps and the `NONE` item,
307             // the `NONE` item passes a null applicationinfo object. NFC uses a different preference
308             // method for adding, and a different onclick method
309             if (applicationInfo != null) {
310                 UserHandle user = UserHandle.getUserHandleForUid(applicationInfo.uid);
311                 roleApplicationPreference.setContentDescription(
312                         AppUtils.getAppContentDescription(
313                                 context, applicationInfo.packageName, user.getIdentifier()));
314                 Bundle extras = preference.getExtras();
315                 extras.putString(PREFERENCE_EXTRA_PACKAGE_NAME, applicationInfo.packageName);
316                 extras.putInt(PREFERENCE_EXTRA_UID, applicationInfo.uid);
317             }
318         } else {
319             preference = roleApplicationPreference.asTwoStatePreference();
320         }
321 
322         preference.setChecked(checked);
323         if (applicationInfo != null) {
324             UserHandle user = UserHandle.getUserHandleForUid(applicationInfo.uid);
325             roleApplicationPreference.setRestrictionIntent(
326                     mRole.getApplicationRestrictionIntentAsUser(applicationInfo, user, context));
327             RoleUiBehaviorUtils.prepareApplicationPreferenceAsUser(mRole, roleApplicationPreference,
328                     applicationInfo, user, context);
329         }
330 
331         preferenceGroup.addPreference(preference);
332     }
333 
onManageRoleHolderStateChanged(int state)334     private void onManageRoleHolderStateChanged(int state) {
335         ManageRoleHolderStateLiveData liveData = mViewModel.getManageRoleHolderStateLiveData();
336         switch (state) {
337             case ManageRoleHolderStateLiveData.STATE_SUCCESS:
338                 String packageName = liveData.getLastPackageName();
339                 if (packageName != null) {
340                     mRole.onHolderSelectedAsUser(packageName, liveData.getLastUser(),
341                             requireContext());
342                 }
343                 liveData.resetState();
344                 break;
345             case ManageRoleHolderStateLiveData.STATE_FAILURE:
346                 liveData.resetState();
347                 break;
348         }
349     }
350 
351     @Override
onPreferenceClick(@onNull Preference preference)352     public boolean onPreferenceClick(@NonNull Preference preference) {
353         String key = preference.getKey();
354         if (Objects.equals(key, PREFERENCE_KEY_NONE)) {
355             PermissionControllerStatsLog.write(
356                     ROLE_SETTINGS_FRAGMENT_ACTION_REPORTED, Process.INVALID_UID, null, mRoleName);
357             mViewModel.setNoneDefaultApp();
358         } else {
359             String packageName =
360                     preference.getExtras().getString(PREFERENCE_EXTRA_PACKAGE_NAME);
361             int uid = preference.getExtras().getInt(PREFERENCE_EXTRA_UID);
362             CharSequence confirmationMessage =
363                     RoleUiBehaviorUtils.getConfirmationMessage(mRole, packageName,
364                             requireContext());
365             if (confirmationMessage != null) {
366                 DefaultAppConfirmationDialogFragment.show(packageName, uid, confirmationMessage,
367                         this);
368             } else {
369                 setDefaultApp(packageName, uid);
370             }
371         }
372         return true;
373     }
374 
375     @Override
setDefaultApp(@onNull String packageName, int uid)376     public void setDefaultApp(@NonNull String packageName, int uid) {
377         PermissionControllerStatsLog.write(
378                 ROLE_SETTINGS_FRAGMENT_ACTION_REPORTED, uid, packageName, mRoleName);
379         mViewModel.setDefaultApp(packageName, UserHandle.getUserHandleForUid(uid));
380     }
381 
addNonPaymentNfcServicesPreference(@onNull PreferenceScreen preferenceScreen, @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context)382     private void addNonPaymentNfcServicesPreference(@NonNull PreferenceScreen preferenceScreen,
383             @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context) {
384         if (!(SdkLevel.isAtLeastV() && Objects.equals(mRoleName, RoleManager.ROLE_WALLET))) {
385             return;
386         }
387 
388         Intent intent = new Intent(SettingsCompat.ACTION_MANAGE_OTHER_NFC_SERVICES_SETTINGS);
389         if (!PackageUtils.isIntentResolvedToSettings(intent, context)) {
390             return;
391         }
392 
393         Preference preference = oldPreferences.get(PREFERENCE_KEY_OTHER_NFC_SERVICES);
394         if (preference == null) {
395             preference = new Preference(context);
396             preference.setKey(PREFERENCE_KEY_OTHER_NFC_SERVICES);
397             preference.setIcon(AppCompatResources.getDrawable(context, R.drawable.ic_nfc));
398             preference.setTitle(context.getString(
399                     R.string.default_payment_app_other_nfc_services));
400             preference.setPersistent(false);
401             preference.setOnPreferenceClickListener(preference2 -> {
402                 context.startActivity(intent);
403                 return true;
404             });
405         }
406 
407         preferenceScreen.addPreference(preference);
408     }
409 
addDescriptionPreference(@onNull PreferenceScreen preferenceScreen, @NonNull ArrayMap<String, Preference> oldPreferences)410     private void addDescriptionPreference(@NonNull PreferenceScreen preferenceScreen,
411             @NonNull ArrayMap<String, Preference> oldPreferences) {
412         Preference preference = oldPreferences.get(PREFERENCE_KEY_DESCRIPTION);
413         if (preference == null) {
414             preference = requirePreferenceFragment().createFooterPreference();
415             preference.setKey(PREFERENCE_KEY_DESCRIPTION);
416             preference.setSummary(mRole.getDescriptionResource());
417         }
418 
419         preferenceScreen.addPreference(preference);
420     }
421 
422     @NonNull
requirePreferenceFragment()423     private PF requirePreferenceFragment() {
424         //noinspection unchecked
425         return (PF) requireParentFragment();
426     }
427 
428     /**
429      * Interface that the parent fragment must implement.
430      */
431     public interface Parent {
432 
433         /**
434          * Set the title of the current settings page.
435          *
436          * @param title the title of the current settings page
437          */
setTitle(@onNull CharSequence title)438         void setTitle(@NonNull CharSequence title);
439 
440         /**
441          * Create a new preference for an application.
442          *
443          * @return a new preference for an application
444          */
445         @NonNull
createApplicationPreference()446         RoleApplicationPreference createApplicationPreference();
447 
448         /**
449          * Create a new preference for the footer.
450          *
451          * @return a new preference for the footer
452          */
453         @NonNull
createFooterPreference()454         Preference createFooterPreference();
455 
456         /**
457          * Callback when changes have been made to the {@link PreferenceScreen} of the parent
458          * {@link PreferenceFragmentCompat}.
459          */
onPreferenceScreenChanged()460         void onPreferenceScreenChanged();
461     }
462 }
463