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 RoleUiBehaviorUtils.prepareApplicationPreferenceAsUser(mRole, roleApplicationPreference, 218 applicationInfo, mUser, context); 219 } 220 221 preferenceScreen.addPreference(preference); 222 } 223 onManageRoleHolderStateChanged(int state)224 private void onManageRoleHolderStateChanged(int state) { 225 ManageRoleHolderStateLiveData liveData = mViewModel.getManageRoleHolderStateLiveData(); 226 switch (state) { 227 case ManageRoleHolderStateLiveData.STATE_SUCCESS: 228 String packageName = liveData.getLastPackageName(); 229 if (packageName != null) { 230 mRole.onHolderSelectedAsUser(packageName, liveData.getLastUser(), 231 requireContext()); 232 } 233 liveData.resetState(); 234 break; 235 case ManageRoleHolderStateLiveData.STATE_FAILURE: 236 liveData.resetState(); 237 break; 238 } 239 } 240 241 @Override onPreferenceClick(@onNull Preference preference)242 public boolean onPreferenceClick(@NonNull Preference preference) { 243 String key = preference.getKey(); 244 if (Objects.equals(key, PREFERENCE_KEY_NONE)) { 245 mViewModel.setNoneDefaultApp(); 246 } else { 247 String packageName = key; 248 CharSequence confirmationMessage = 249 RoleUiBehaviorUtils.getConfirmationMessage(mRole, packageName, 250 requireContext()); 251 if (confirmationMessage != null) { 252 DefaultAppConfirmationDialogFragment.show(packageName, confirmationMessage, this); 253 } else { 254 setDefaultApp(packageName); 255 } 256 } 257 return true; 258 } 259 260 @Override setDefaultApp(@onNull String packageName)261 public void setDefaultApp(@NonNull String packageName) { 262 mViewModel.setDefaultApp(packageName); 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 * Set the title of the current settings page. 278 * 279 * @param title the title of the current settings page 280 */ setTitle(@onNull CharSequence title)281 void setTitle(@NonNull CharSequence title); 282 283 /** 284 * Create a new preference for an application. 285 * 286 * @return a new preference for an application 287 */ 288 @NonNull createApplicationPreference()289 RoleApplicationPreference createApplicationPreference(); 290 291 /** 292 * Create a new preference for the footer. 293 * 294 * @return a new preference for the footer 295 */ 296 @NonNull createFooterPreference()297 Preference createFooterPreference(); 298 299 /** 300 * Callback when changes have been made to the {@link PreferenceScreen} of the parent 301 * {@link PreferenceFragmentCompat}. 302 */ onPreferenceScreenChanged()303 void onPreferenceScreenChanged(); 304 } 305 } 306