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