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 android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageManager;
24 import android.os.Bundle;
25 import android.os.UserHandle;
26 import android.provider.Settings;
27 import android.util.ArrayMap;
28 
29 import androidx.annotation.NonNull;
30 import androidx.annotation.Nullable;
31 import androidx.fragment.app.Fragment;
32 import androidx.lifecycle.ViewModelProviders;
33 import androidx.preference.Preference;
34 import androidx.preference.PreferenceCategory;
35 import androidx.preference.PreferenceFragmentCompat;
36 import androidx.preference.PreferenceGroup;
37 import androidx.preference.PreferenceManager;
38 import androidx.preference.PreferenceScreen;
39 
40 import com.android.permissioncontroller.R;
41 import com.android.permissioncontroller.permission.utils.Utils;
42 import com.android.permissioncontroller.role.model.Role;
43 import com.android.permissioncontroller.role.model.Roles;
44 
45 import java.util.List;
46 import java.util.Objects;
47 
48 /**
49  * Child fragment for the list of default apps.
50  * <p>
51  * Must be added as a child fragment and its parent fragment must be a
52  * {@link PreferenceFragmentCompat} that implements {@link Parent}.
53  *
54  * @param <PF> type of the parent fragment
55  */
56 public class DefaultAppListChildFragment<PF extends PreferenceFragmentCompat
57         & DefaultAppListChildFragment.Parent> extends Fragment
58         implements Preference.OnPreferenceClickListener {
59 
60     private static final String PREFERENCE_KEY_MORE_DEFAULT_APPS =
61             DefaultAppListChildFragment.class.getName() + ".preference.MORE_DEFAULT_APPS";
62     private static final String PREFERENCE_KEY_MANAGE_DOMAIN_URLS =
63             DefaultAppListChildFragment.class.getName() + ".preference.MANAGE_DOMAIN_URLS";
64     private static final String PREFERENCE_KEY_WORK_CATEGORY =
65             DefaultAppListChildFragment.class.getName() + ".preference.WORK_CATEGORY";
66 
67     @NonNull
68     private DefaultAppListViewModel mViewModel;
69 
70     /**
71      * Create a new instance of this fragment.
72      *
73      * @return a new instance of this fragment
74      */
75     @NonNull
newInstance()76     public static DefaultAppListChildFragment newInstance() {
77         return new DefaultAppListChildFragment();
78     }
79 
80     @Override
onActivityCreated(@ullable Bundle savedInstanceState)81     public void onActivityCreated(@Nullable Bundle savedInstanceState) {
82         super.onActivityCreated(savedInstanceState);
83 
84         mViewModel = ViewModelProviders.of(this).get(DefaultAppListViewModel.class);
85         mViewModel.getLiveData().observe(this, roleItems -> onRoleListChanged());
86         if (mViewModel.hasWorkProfile()) {
87             mViewModel.getWorkLiveData().observe(this, roleItems -> onRoleListChanged());
88         }
89     }
90 
onRoleListChanged()91     private void onRoleListChanged() {
92         List<RoleItem> roleItems = mViewModel.getLiveData().getValue();
93         if (roleItems == null) {
94             return;
95         }
96         boolean hasWorkProfile = mViewModel.hasWorkProfile();
97         List<RoleItem> workRoleItems = null;
98         if (hasWorkProfile) {
99             workRoleItems = mViewModel.getWorkLiveData().getValue();
100             if (workRoleItems == null) {
101                 return;
102             }
103         }
104 
105         PF preferenceFragment = requirePreferenceFragment();
106         PreferenceManager preferenceManager = preferenceFragment.getPreferenceManager();
107         Context context = preferenceManager.getContext();
108         PreferenceScreen preferenceScreen = preferenceFragment.getPreferenceScreen();
109         ArrayMap<String, Preference> oldPreferences = new ArrayMap<>();
110         PreferenceCategory oldWorkPreferenceCategory = null;
111         ArrayMap<String, Preference> oldWorkPreferences = new ArrayMap<>();
112         if (preferenceScreen == null) {
113             preferenceScreen = preferenceManager.createPreferenceScreen(context);
114             preferenceFragment.setPreferenceScreen(preferenceScreen);
115         } else {
116             oldWorkPreferenceCategory = preferenceScreen.findPreference(
117                     PREFERENCE_KEY_WORK_CATEGORY);
118             if (oldWorkPreferenceCategory != null) {
119                 clearPreferences(oldWorkPreferenceCategory, oldWorkPreferences);
120                 preferenceScreen.removePreference(oldWorkPreferenceCategory);
121                 oldWorkPreferenceCategory.setOrder(Preference.DEFAULT_ORDER);
122             }
123             clearPreferences(preferenceScreen, oldPreferences);
124         }
125 
126         addPreferences(preferenceScreen, roleItems, oldPreferences, this, mViewModel.getUser(),
127                 context);
128         addMoreDefaultAppsPreference(preferenceScreen, oldPreferences, context);
129         addManageDomainUrlsPreference(preferenceScreen, oldPreferences, context);
130         if (hasWorkProfile && !workRoleItems.isEmpty()) {
131             PreferenceCategory workPreferenceCategory = oldWorkPreferenceCategory;
132             if (workPreferenceCategory == null) {
133                 workPreferenceCategory = new PreferenceCategory(context);
134                 workPreferenceCategory.setKey(PREFERENCE_KEY_WORK_CATEGORY);
135                 workPreferenceCategory.setTitle(R.string.default_apps_for_work);
136             }
137             preferenceScreen.addPreference(workPreferenceCategory);
138             addPreferences(workPreferenceCategory, workRoleItems, oldWorkPreferences, this,
139                     mViewModel.getWorkProfile(), context);
140         }
141 
142         preferenceFragment.onPreferenceScreenChanged();
143     }
144 
clearPreferences(@onNull PreferenceGroup preferenceGroup, @NonNull ArrayMap<String, Preference> oldPreferences)145     private static void clearPreferences(@NonNull PreferenceGroup preferenceGroup,
146             @NonNull ArrayMap<String, Preference> oldPreferences) {
147         for (int i = preferenceGroup.getPreferenceCount() - 1; i >= 0; --i) {
148             Preference preference = preferenceGroup.getPreference(i);
149 
150             preferenceGroup.removePreference(preference);
151             preference.setOrder(Preference.DEFAULT_ORDER);
152             oldPreferences.put(preference.getKey(), preference);
153         }
154     }
155 
addPreferences(@onNull PreferenceGroup preferenceGroup, @NonNull List<RoleItem> roleItems, @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Preference.OnPreferenceClickListener listener, @NonNull UserHandle user, @NonNull Context context)156     private void addPreferences(@NonNull PreferenceGroup preferenceGroup,
157             @NonNull List<RoleItem> roleItems, @NonNull ArrayMap<String, Preference> oldPreferences,
158             @NonNull Preference.OnPreferenceClickListener listener, @NonNull UserHandle user,
159             @NonNull Context context) {
160         PF preferenceFragment = requirePreferenceFragment();
161         int roleItemsSize = roleItems.size();
162         for (int i = 0; i < roleItemsSize; i++) {
163             RoleItem roleItem = roleItems.get(i);
164 
165             Role role = roleItem.getRole();
166             Preference preference = oldPreferences.get(role.getName());
167             if (preference == null) {
168                 preference = (Preference) preferenceFragment.createPreference(context);
169                 preference.setKey(role.getName());
170                 preference.setIconSpaceReserved(true);
171                 preference.setTitle(role.getShortLabelResource());
172                 preference.setPersistent(false);
173                 preference.setOnPreferenceClickListener(listener);
174                 preference.getExtras().putParcelable(Intent.EXTRA_USER, user);
175             }
176 
177             List<ApplicationInfo> holderApplicationInfos = roleItem.getHolderApplicationInfos();
178             if (holderApplicationInfos.isEmpty()) {
179                 preference.setIcon(null);
180                 preference.setSummary(R.string.default_app_none);
181             } else {
182                 ApplicationInfo holderApplicationInfo = holderApplicationInfos.get(0);
183                 preference.setIcon(Utils.getBadgedIcon(context, holderApplicationInfo));
184                 preference.setSummary(Utils.getAppLabel(holderApplicationInfo, context));
185             }
186             role.preparePreferenceAsUser((TwoTargetPreference) preference, user, context);
187 
188             preferenceGroup.addPreference(preference);
189         }
190     }
191 
192     @Override
onPreferenceClick(@onNull Preference preference)193     public boolean onPreferenceClick(@NonNull Preference preference) {
194         String roleName = preference.getKey();
195         Context context = requireContext();
196         Role role = Roles.get(context).get(roleName);
197         UserHandle user = preference.getExtras().getParcelable(Intent.EXTRA_USER);
198         Intent intent = role.getManageIntentAsUser(user, context);
199         if (intent == null) {
200             intent = DefaultAppActivity.createIntent(roleName, user, context);
201         }
202         startActivity(intent);
203         return true;
204     }
205 
addMoreDefaultAppsPreference(@onNull PreferenceGroup preferenceGroup, @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context)206     private static void addMoreDefaultAppsPreference(@NonNull PreferenceGroup preferenceGroup,
207             @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context) {
208         Intent intent = new Intent(Settings.ACTION_MANAGE_MORE_DEFAULT_APPS_SETTINGS);
209         if (!isIntentResolvedToSettings(intent, context)) {
210             return;
211         }
212 
213         Preference preference = oldPreferences.get(PREFERENCE_KEY_MORE_DEFAULT_APPS);
214         if (preference == null) {
215             preference = new Preference(context);
216             preference.setKey(PREFERENCE_KEY_MORE_DEFAULT_APPS);
217             preference.setIconSpaceReserved(true);
218             preference.setTitle(context.getString(R.string.default_apps_more));
219             preference.setPersistent(false);
220             preference.setOnPreferenceClickListener(preference2 -> {
221                 context.startActivity(intent);
222                 return true;
223             });
224         }
225 
226         preferenceGroup.addPreference(preference);
227     }
228 
addManageDomainUrlsPreference(@onNull PreferenceGroup preferenceGroup, @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context)229     private static void addManageDomainUrlsPreference(@NonNull PreferenceGroup preferenceGroup,
230             @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context) {
231         Intent intent = new Intent(Settings.ACTION_MANAGE_DOMAIN_URLS);
232         if (!isIntentResolvedToSettings(intent, context)) {
233             return;
234         }
235 
236         Preference preference = oldPreferences.get(PREFERENCE_KEY_MANAGE_DOMAIN_URLS);
237         if (preference == null) {
238             preference = new Preference(context);
239             preference.setKey(PREFERENCE_KEY_MANAGE_DOMAIN_URLS);
240             preference.setIconSpaceReserved(true);
241             preference.setTitle(context.getString(R.string.default_apps_manage_domain_urls));
242             preference.setPersistent(false);
243             preference.setOnPreferenceClickListener(preference2 -> {
244                 context.startActivity(intent);
245                 return true;
246             });
247         }
248 
249         preferenceGroup.addPreference(preference);
250     }
251 
isIntentResolvedToSettings(@onNull Intent intent, @NonNull Context context)252     private static boolean isIntentResolvedToSettings(@NonNull Intent intent,
253             @NonNull Context context) {
254         PackageManager packageManager = context.getPackageManager();
255         ComponentName componentName = intent.resolveActivity(packageManager);
256         if (componentName == null) {
257             return false;
258         }
259         Intent settingsIntent = new Intent(Settings.ACTION_SETTINGS);
260         String settingsPackageName = settingsIntent.resolveActivity(packageManager)
261                 .getPackageName();
262         return Objects.equals(componentName.getPackageName(), settingsPackageName);
263     }
264 
265     @NonNull
requirePreferenceFragment()266     private PF requirePreferenceFragment() {
267         //noinspection unchecked
268         return (PF) requireParentFragment();
269     }
270 
271     /**
272      * Interface that the parent fragment must implement.
273      */
274     public interface Parent {
275 
276         /**
277          * Create a new preference for a default app.
278          *
279          * @param context the {@code Context} to use when creating the preference.
280          *
281          * @return a new preference for a default app
282          */
283         @NonNull
createPreference(@onNull Context context)284         TwoTargetPreference createPreference(@NonNull Context context);
285 
286         /**
287          * Callback when changes have been made to the {@link PreferenceScreen} of the parent
288          * {@link PreferenceFragmentCompat}.
289          */
onPreferenceScreenChanged()290         void onPreferenceScreenChanged();
291     }
292 }
293