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.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.ApplicationInfo; 23 import android.graphics.drawable.Drawable; 24 import android.os.Bundle; 25 import android.os.UserHandle; 26 import android.util.ArrayMap; 27 import android.util.Pair; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 import androidx.appcompat.content.res.AppCompatResources; 32 import androidx.fragment.app.Fragment; 33 import androidx.lifecycle.ViewModelProviders; 34 import androidx.preference.Preference; 35 import androidx.preference.PreferenceFragmentCompat; 36 import androidx.preference.PreferenceManager; 37 import androidx.preference.PreferenceScreen; 38 import androidx.preference.TwoStatePreference; 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 a default app. 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 DefaultAppChildFragment<PF extends PreferenceFragmentCompat 57 & DefaultAppChildFragment.Parent> extends Fragment 58 implements DefaultAppConfirmationDialogFragment.Listener, 59 Preference.OnPreferenceClickListener { 60 61 private static final String PREFERENCE_KEY_NONE = DefaultAppChildFragment.class.getName() 62 + ".preference.NONE"; 63 private static final String PREFERENCE_KEY_DESCRIPTION = DefaultAppChildFragment.class.getName() 64 + ".preference.DESCRIPTION"; 65 66 @NonNull 67 private String mRoleName; 68 @NonNull 69 private UserHandle mUser; 70 71 @NonNull 72 private Role mRole; 73 74 @NonNull 75 private DefaultAppViewModel mViewModel; 76 77 /** 78 * Create a new instance of this fragment. 79 * 80 * @param roleName the name of the role for the default app 81 * @param user the user for the default app 82 * 83 * @return a new instance of this fragment 84 */ 85 @NonNull newInstance(@onNull String roleName, @NonNull UserHandle user)86 public static DefaultAppChildFragment newInstance(@NonNull String roleName, 87 @NonNull UserHandle user) { 88 DefaultAppChildFragment fragment = new DefaultAppChildFragment(); 89 Bundle arguments = new Bundle(); 90 arguments.putString(Intent.EXTRA_ROLE_NAME, roleName); 91 arguments.putParcelable(Intent.EXTRA_USER, user); 92 fragment.setArguments(arguments); 93 return fragment; 94 } 95 96 @Override onCreate(@ullable Bundle savedInstanceState)97 public void onCreate(@Nullable Bundle savedInstanceState) { 98 super.onCreate(savedInstanceState); 99 100 Bundle arguments = getArguments(); 101 mRoleName = arguments.getString(Intent.EXTRA_ROLE_NAME); 102 mUser = arguments.getParcelable(Intent.EXTRA_USER); 103 } 104 105 @Override onActivityCreated(@ullable Bundle savedInstanceState)106 public void onActivityCreated(@Nullable Bundle savedInstanceState) { 107 super.onActivityCreated(savedInstanceState); 108 109 PF preferenceFragment = requirePreferenceFragment(); 110 Activity activity = requireActivity(); 111 mRole = Roles.get(activity).get(mRoleName); 112 preferenceFragment.setTitle(getString(mRole.getLabelResource())); 113 114 mViewModel = ViewModelProviders.of(this, new DefaultAppViewModel.Factory(mRole, mUser, 115 activity.getApplication())).get(DefaultAppViewModel.class); 116 mViewModel.getRoleLiveData().observe(this, this::onRoleChanged); 117 mViewModel.getManageRoleHolderStateLiveData().observe(this, 118 this::onManageRoleHolderStateChanged); 119 } 120 onRoleChanged( @onNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications)121 private void onRoleChanged( 122 @NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) { 123 PF preferenceFragment = requirePreferenceFragment(); 124 PreferenceManager preferenceManager = preferenceFragment.getPreferenceManager(); 125 Context context = preferenceManager.getContext(); 126 127 PreferenceScreen preferenceScreen = preferenceFragment.getPreferenceScreen(); 128 Preference oldDescriptionPreference = null; 129 ArrayMap<String, Preference> oldPreferences = new ArrayMap<>(); 130 if (preferenceScreen == null) { 131 preferenceScreen = preferenceManager.createPreferenceScreen(context); 132 preferenceFragment.setPreferenceScreen(preferenceScreen); 133 } else { 134 oldDescriptionPreference = preferenceScreen.findPreference(PREFERENCE_KEY_DESCRIPTION); 135 if (oldDescriptionPreference != null) { 136 preferenceScreen.removePreference(oldDescriptionPreference); 137 oldDescriptionPreference.setOrder(Preference.DEFAULT_ORDER); 138 } 139 for (int i = preferenceScreen.getPreferenceCount() - 1; i >= 0; --i) { 140 Preference preference = preferenceScreen.getPreference(i); 141 142 preferenceScreen.removePreference(preference); 143 preference.setOrder(Preference.DEFAULT_ORDER); 144 oldPreferences.put(preference.getKey(), preference); 145 } 146 } 147 148 if (mRole.shouldShowNone()) { 149 Drawable icon = AppCompatResources.getDrawable(context, R.drawable.ic_remove_circle); 150 String title = getString(R.string.default_app_none); 151 boolean noHolderApplication = !hasHolderApplication(qualifyingApplications); 152 addPreference(PREFERENCE_KEY_NONE, icon, title, noHolderApplication, null, 153 oldPreferences, preferenceScreen, context); 154 } 155 156 int qualifyingApplicationsSize = qualifyingApplications.size(); 157 for (int i = 0; i < qualifyingApplicationsSize; i++) { 158 Pair<ApplicationInfo, Boolean> qualifyingApplication = qualifyingApplications.get(i); 159 ApplicationInfo qualifyingApplicationInfo = qualifyingApplication.first; 160 boolean isHolderApplication = qualifyingApplication.second; 161 162 String key = qualifyingApplicationInfo.packageName; 163 Drawable icon = Utils.getBadgedIcon(context, qualifyingApplicationInfo); 164 String title = Utils.getFullAppLabel(qualifyingApplicationInfo, context); 165 addPreference(key, icon, title, isHolderApplication, qualifyingApplicationInfo, 166 oldPreferences, preferenceScreen, context); 167 } 168 169 Preference descriptionPreference = oldDescriptionPreference; 170 if (descriptionPreference == null) { 171 descriptionPreference = preferenceFragment.createFooterPreference(context); 172 descriptionPreference.setKey(PREFERENCE_KEY_DESCRIPTION); 173 descriptionPreference.setSummary(mRole.getDescriptionResource()); 174 } 175 preferenceScreen.addPreference(descriptionPreference); 176 177 preferenceFragment.onPreferenceScreenChanged(); 178 } 179 hasHolderApplication( @onNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications)180 private static boolean hasHolderApplication( 181 @NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) { 182 int qualifyingApplicationsSize = qualifyingApplications.size(); 183 for (int i = 0; i < qualifyingApplicationsSize; i++) { 184 Pair<ApplicationInfo, Boolean> qualifyingApplication = qualifyingApplications.get(i); 185 boolean isHolderApplication = qualifyingApplication.second; 186 187 if (isHolderApplication) { 188 return true; 189 } 190 } 191 return false; 192 } 193 addPreference(@onNull String key, @NonNull Drawable icon, @NonNull CharSequence title, boolean checked, @Nullable ApplicationInfo applicationInfo, @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull PreferenceScreen preferenceScreen, @NonNull Context context)194 private void addPreference(@NonNull String key, @NonNull Drawable icon, 195 @NonNull CharSequence title, boolean checked, @Nullable ApplicationInfo applicationInfo, 196 @NonNull ArrayMap<String, Preference> oldPreferences, 197 @NonNull PreferenceScreen preferenceScreen, @NonNull Context context) { 198 TwoStatePreference preference = (TwoStatePreference) oldPreferences.get(key); 199 if (preference == null) { 200 preference = requirePreferenceFragment().createApplicationPreference(context); 201 preference.setKey(key); 202 preference.setIcon(icon); 203 preference.setTitle(title); 204 preference.setPersistent(false); 205 preference.setOnPreferenceChangeListener((preference2, newValue) -> false); 206 preference.setOnPreferenceClickListener(this); 207 } 208 209 preference.setChecked(checked); 210 if (applicationInfo != null) { 211 mRole.prepareApplicationPreferenceAsUser(preference, applicationInfo, mUser, context); 212 } 213 214 preferenceScreen.addPreference(preference); 215 } 216 onManageRoleHolderStateChanged(int state)217 private void onManageRoleHolderStateChanged(int state) { 218 ManageRoleHolderStateLiveData liveData = mViewModel.getManageRoleHolderStateLiveData(); 219 switch (state) { 220 case ManageRoleHolderStateLiveData.STATE_SUCCESS: 221 String packageName = liveData.getLastPackageName(); 222 if (packageName != null) { 223 mRole.onHolderSelectedAsUser(packageName, liveData.getLastUser(), 224 requireContext()); 225 } 226 liveData.resetState(); 227 break; 228 case ManageRoleHolderStateLiveData.STATE_FAILURE: 229 liveData.resetState(); 230 break; 231 } 232 } 233 234 @Override onPreferenceClick(@onNull Preference preference)235 public boolean onPreferenceClick(@NonNull Preference preference) { 236 String key = preference.getKey(); 237 if (Objects.equals(key, PREFERENCE_KEY_NONE)) { 238 mViewModel.setNoneDefaultApp(); 239 } else { 240 String packageName = key; 241 CharSequence confirmationMessage = mRole.getConfirmationMessage(packageName, 242 requireContext()); 243 if (confirmationMessage != null) { 244 DefaultAppConfirmationDialogFragment.show(packageName, confirmationMessage, this); 245 } else { 246 setDefaultApp(packageName); 247 } 248 } 249 return true; 250 } 251 252 @Override setDefaultApp(@onNull String packageName)253 public void setDefaultApp(@NonNull String packageName) { 254 mViewModel.setDefaultApp(packageName); 255 } 256 257 @NonNull requirePreferenceFragment()258 private PF requirePreferenceFragment() { 259 //noinspection unchecked 260 return (PF) requireParentFragment(); 261 } 262 263 /** 264 * Interface that the parent fragment must implement. 265 */ 266 public interface Parent { 267 268 /** 269 * Set the title of the current settings page. 270 * 271 * @param title the title of the current settings page 272 */ setTitle(@onNull CharSequence title)273 void setTitle(@NonNull CharSequence title); 274 275 /** 276 * Create a new preference for an application. 277 * 278 * @param context the {@code Context} to use when creating the preference. 279 * 280 * @return a new preference for an application 281 */ 282 @NonNull createApplicationPreference(@onNull Context context)283 TwoStatePreference createApplicationPreference(@NonNull Context context); 284 285 /** 286 * Create a new preference for the footer. 287 * 288 * @param context the {@code Context} to use when creating the preference. 289 * 290 * @return a new preference for the footer 291 */ 292 @NonNull createFooterPreference(@onNull Context context)293 Preference createFooterPreference(@NonNull Context context); 294 295 /** 296 * Callback when changes have been made to the {@link PreferenceScreen} of the parent 297 * {@link PreferenceFragmentCompat}. 298 */ onPreferenceScreenChanged()299 void onPreferenceScreenChanged(); 300 } 301 } 302