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.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.packageinstaller.permission.utils.Utils; 41 import com.android.packageinstaller.role.model.Role; 42 import com.android.packageinstaller.role.model.Roles; 43 import com.android.permissioncontroller.R; 44 45 import java.util.List; 46 import java.util.Objects; 47 48 /** 49 * Child fragment for the list of default apps. Must be added as a child fragment and its parent 50 * fragment must be a {@link PreferenceFragmentCompat} which implements {@link Parent}. 51 * 52 * @param <PF> type of the parent fragment 53 */ 54 public class DefaultAppListChildFragment<PF extends PreferenceFragmentCompat 55 & DefaultAppListChildFragment.Parent> extends Fragment 56 implements Preference.OnPreferenceClickListener { 57 58 private static final String PREFERENCE_KEY_MORE_DEFAULT_APPS = 59 DefaultAppListChildFragment.class.getName() + ".preference.MORE_DEFAULT_APPS"; 60 61 private static final String PREFERENCE_KEY_MANAGE_DOMAIN_URLS = 62 DefaultAppListChildFragment.class.getName() + ".preference.MANAGE_DOMAIN_URLS"; 63 64 private static final String PREFERENCE_KEY_WORK_CATEGORY = 65 DefaultAppListChildFragment.class.getName() + ".preference.WORK_CATEGORY"; 66 67 private DefaultAppListViewModel mViewModel; 68 69 /** 70 * Create a new instance of this fragment. 71 * 72 * @return a new instance of this fragment 73 */ 74 @NonNull newInstance()75 public static DefaultAppListChildFragment newInstance() { 76 return new DefaultAppListChildFragment(); 77 } 78 79 @Override onActivityCreated(@ullable Bundle savedInstanceState)80 public void onActivityCreated(@Nullable Bundle savedInstanceState) { 81 super.onActivityCreated(savedInstanceState); 82 83 mViewModel = ViewModelProviders.of(this).get(DefaultAppListViewModel.class); 84 mViewModel.getLiveData().observe(this, roleItems -> onRoleListChanged()); 85 if (mViewModel.hasWorkProfile()) { 86 mViewModel.getWorkLiveData().observe(this, roleItems -> onRoleListChanged()); 87 } 88 } 89 onRoleListChanged()90 private void onRoleListChanged() { 91 List<RoleItem> roleItems = mViewModel.getLiveData().getValue(); 92 if (roleItems == null) { 93 return; 94 } 95 boolean hasWorkProfile = mViewModel.hasWorkProfile(); 96 List<RoleItem> workRoleItems = null; 97 if (hasWorkProfile) { 98 workRoleItems = mViewModel.getWorkLiveData().getValue(); 99 if (workRoleItems == null) { 100 return; 101 } 102 } 103 104 PF preferenceFragment = requirePreferenceFragment(); 105 PreferenceManager preferenceManager = preferenceFragment.getPreferenceManager(); 106 Context context = preferenceManager.getContext(); 107 PreferenceScreen preferenceScreen = preferenceFragment.getPreferenceScreen(); 108 ArrayMap<String, Preference> oldPreferences = new ArrayMap<>(); 109 PreferenceCategory oldWorkPreferenceCategory = null; 110 ArrayMap<String, Preference> oldWorkPreferences = new ArrayMap<>(); 111 if (preferenceScreen == null) { 112 preferenceScreen = preferenceManager.createPreferenceScreen(context); 113 preferenceFragment.setPreferenceScreen(preferenceScreen); 114 } else { 115 oldWorkPreferenceCategory = preferenceScreen.findPreference( 116 PREFERENCE_KEY_WORK_CATEGORY); 117 if (oldWorkPreferenceCategory != null) { 118 clearPreferences(oldWorkPreferenceCategory, oldWorkPreferences); 119 preferenceScreen.removePreference(oldWorkPreferenceCategory); 120 oldWorkPreferenceCategory.setOrder(Preference.DEFAULT_ORDER); 121 } 122 clearPreferences(preferenceScreen, oldPreferences); 123 } 124 125 addPreferences(preferenceScreen, roleItems, oldPreferences, this, mViewModel.getUser(), 126 context); 127 addMoreDefaultAppsPreference(preferenceScreen, oldPreferences, context); 128 addManageDomainUrlsPreference(preferenceScreen, oldPreferences, context); 129 if (hasWorkProfile && !workRoleItems.isEmpty()) { 130 PreferenceCategory workPreferenceCategory = oldWorkPreferenceCategory; 131 if (workPreferenceCategory == null) { 132 workPreferenceCategory = new PreferenceCategory(context); 133 workPreferenceCategory.setKey(PREFERENCE_KEY_WORK_CATEGORY); 134 workPreferenceCategory.setTitle(R.string.default_apps_for_work); 135 } 136 preferenceScreen.addPreference(workPreferenceCategory); 137 addPreferences(workPreferenceCategory, workRoleItems, oldWorkPreferences, this, 138 mViewModel.getWorkProfile(), context); 139 } 140 141 preferenceFragment.onPreferenceScreenChanged(); 142 } 143 clearPreferences(@onNull PreferenceGroup preferenceGroup, @NonNull ArrayMap<String, Preference> oldPreferences)144 private static void clearPreferences(@NonNull PreferenceGroup preferenceGroup, 145 @NonNull ArrayMap<String, Preference> oldPreferences) { 146 for (int i = preferenceGroup.getPreferenceCount() - 1; i >= 0; --i) { 147 Preference preference = preferenceGroup.getPreference(i); 148 149 preferenceGroup.removePreference(preference); 150 preference.setOrder(Preference.DEFAULT_ORDER); 151 oldPreferences.put(preference.getKey(), preference); 152 } 153 } 154 addPreferences(@onNull PreferenceGroup preferenceGroup, @NonNull List<RoleItem> roleItems, @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Preference.OnPreferenceClickListener listener, @NonNull UserHandle user, @NonNull Context context)155 private void addPreferences(@NonNull PreferenceGroup preferenceGroup, 156 @NonNull List<RoleItem> roleItems, @NonNull ArrayMap<String, Preference> oldPreferences, 157 @NonNull Preference.OnPreferenceClickListener listener, @NonNull UserHandle user, 158 @NonNull Context context) { 159 PF preferenceFragment = requirePreferenceFragment(); 160 int roleItemsSize = roleItems.size(); 161 for (int i = 0; i < roleItemsSize; i++) { 162 RoleItem roleItem = roleItems.get(i); 163 164 Role role = roleItem.getRole(); 165 TwoTargetPreference preference = (TwoTargetPreference) oldPreferences.get( 166 role.getName()); 167 if (preference == null) { 168 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(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