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