1 /* 2 * Copyright (C) 2019 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.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.ApplicationInfo; 23 import android.os.Bundle; 24 import android.os.UserHandle; 25 import android.util.ArrayMap; 26 import android.util.Pair; 27 28 import androidx.annotation.NonNull; 29 import androidx.annotation.Nullable; 30 import androidx.fragment.app.Fragment; 31 import androidx.lifecycle.ViewModelProviders; 32 import androidx.preference.Preference; 33 import androidx.preference.PreferenceFragmentCompat; 34 import androidx.preference.PreferenceManager; 35 import androidx.preference.PreferenceScreen; 36 import androidx.preference.TwoStatePreference; 37 38 import com.android.packageinstaller.permission.utils.Utils; 39 import com.android.packageinstaller.role.model.Role; 40 import com.android.packageinstaller.role.model.Roles; 41 42 import java.util.List; 43 44 /** 45 * Child fragment for a special app access. Must be added as a child fragment and its parent 46 * fragment must be a {@link PreferenceFragmentCompat} which implements {@link Parent}. 47 * 48 * @param <PF> type of the parent fragment 49 */ 50 public class SpecialAppAccessChildFragment<PF extends PreferenceFragmentCompat 51 & SpecialAppAccessChildFragment.Parent> extends Fragment 52 implements Preference.OnPreferenceClickListener { 53 54 private static final String PREFERENCE_EXTRA_APPLICATION_INFO = 55 SpecialAppAccessChildFragment.class.getName() + ".extra.APPLICATION_INFO"; 56 57 private static final String PREFERENCE_KEY_DESCRIPTION = 58 SpecialAppAccessChildFragment.class.getName() + ".preference.DESCRIPTION"; 59 60 private String mRoleName; 61 62 private Role mRole; 63 64 private SpecialAppAccessViewModel mViewModel; 65 66 /** 67 * Create a new instance of this fragment. 68 * 69 * @param roleName the name of the role for the special app access 70 * 71 * @return a new instance of this fragment 72 */ 73 @NonNull newInstance(@onNull String roleName)74 public static SpecialAppAccessChildFragment newInstance(@NonNull String roleName) { 75 SpecialAppAccessChildFragment fragment = new SpecialAppAccessChildFragment(); 76 Bundle arguments = new Bundle(); 77 arguments.putString(Intent.EXTRA_ROLE_NAME, roleName); 78 fragment.setArguments(arguments); 79 return fragment; 80 } 81 82 @Override onCreate(@ullable Bundle savedInstanceState)83 public void onCreate(@Nullable Bundle savedInstanceState) { 84 super.onCreate(savedInstanceState); 85 86 Bundle arguments = getArguments(); 87 mRoleName = arguments.getString(Intent.EXTRA_ROLE_NAME); 88 } 89 90 @Override onActivityCreated(@ullable Bundle savedInstanceState)91 public void onActivityCreated(@Nullable Bundle savedInstanceState) { 92 super.onActivityCreated(savedInstanceState); 93 94 PF preferenceFragment = requirePreferenceFragment(); 95 Activity activity = requireActivity(); 96 mRole = Roles.get(activity).get(mRoleName); 97 preferenceFragment.setTitle(getString(mRole.getLabelResource())); 98 99 mViewModel = ViewModelProviders.of(this, new SpecialAppAccessViewModel.Factory(mRole, 100 activity.getApplication())).get(SpecialAppAccessViewModel.class); 101 mViewModel.getRoleLiveData().observe(this, this::onRoleChanged); 102 mViewModel.observeManageRoleHolderState(this, this::onManageRoleHolderStateChanged); 103 } 104 onRoleChanged( @onNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications)105 private void onRoleChanged( 106 @NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) { 107 PF preferenceFragment = requirePreferenceFragment(); 108 PreferenceManager preferenceManager = preferenceFragment.getPreferenceManager(); 109 Context context = preferenceManager.getContext(); 110 111 PreferenceScreen preferenceScreen = preferenceFragment.getPreferenceScreen(); 112 Preference oldDescriptionPreference = null; 113 ArrayMap<String, Preference> oldPreferences = new ArrayMap<>(); 114 if (preferenceScreen == null) { 115 preferenceScreen = preferenceManager.createPreferenceScreen(context); 116 preferenceFragment.setPreferenceScreen(preferenceScreen); 117 } else { 118 oldDescriptionPreference = preferenceScreen.findPreference(PREFERENCE_KEY_DESCRIPTION); 119 if (oldDescriptionPreference != null) { 120 preferenceScreen.removePreference(oldDescriptionPreference); 121 oldDescriptionPreference.setOrder(Preference.DEFAULT_ORDER); 122 } 123 for (int i = preferenceScreen.getPreferenceCount() - 1; i >= 0; --i) { 124 Preference preference = preferenceScreen.getPreference(i); 125 126 preferenceScreen.removePreference(preference); 127 preference.setOrder(Preference.DEFAULT_ORDER); 128 oldPreferences.put(preference.getKey(), preference); 129 } 130 } 131 132 int qualifyingApplicationsSize = qualifyingApplications.size(); 133 for (int i = 0; i < qualifyingApplicationsSize; i++) { 134 Pair<ApplicationInfo, Boolean> qualifyingApplication = qualifyingApplications.get(i); 135 ApplicationInfo qualifyingApplicationInfo = qualifyingApplication.first; 136 boolean isHolderPackage = qualifyingApplication.second; 137 138 String key = qualifyingApplicationInfo.packageName + '_' 139 + qualifyingApplicationInfo.uid; 140 TwoStatePreference preference = (TwoStatePreference) oldPreferences.get(key); 141 if (preference == null) { 142 preference = preferenceFragment.createApplicationPreference(context); 143 preference.setKey(key); 144 preference.setIcon(Utils.getBadgedIcon(context, qualifyingApplicationInfo)); 145 preference.setTitle(Utils.getFullAppLabel(qualifyingApplicationInfo, context)); 146 preference.setPersistent(false); 147 preference.setOnPreferenceChangeListener((preference2, newValue) -> false); 148 preference.setOnPreferenceClickListener(this); 149 preference.getExtras().putParcelable(PREFERENCE_EXTRA_APPLICATION_INFO, 150 qualifyingApplicationInfo); 151 } 152 153 preference.setChecked(isHolderPackage); 154 UserHandle user = UserHandle.getUserHandleForUid(qualifyingApplicationInfo.uid); 155 mRole.prepareApplicationPreferenceAsUser(preference, qualifyingApplicationInfo, user, 156 context); 157 158 preferenceScreen.addPreference(preference); 159 } 160 161 Preference descriptionPreference = oldDescriptionPreference; 162 if (descriptionPreference == null) { 163 descriptionPreference = preferenceFragment.createFooterPreference(context); 164 descriptionPreference.setKey(PREFERENCE_KEY_DESCRIPTION); 165 descriptionPreference.setSummary(mRole.getDescriptionResource()); 166 } 167 preferenceScreen.addPreference(descriptionPreference); 168 169 preferenceFragment.onPreferenceScreenChanged(); 170 } 171 onManageRoleHolderStateChanged(@onNull ManageRoleHolderStateLiveData liveData, int state)172 private void onManageRoleHolderStateChanged(@NonNull ManageRoleHolderStateLiveData liveData, 173 int state) { 174 switch (state) { 175 case ManageRoleHolderStateLiveData.STATE_SUCCESS: 176 String packageName = liveData.getLastPackageName(); 177 if (packageName != null && liveData.isLastAdd()) { 178 mRole.onHolderSelectedAsUser(packageName, liveData.getLastUser(), 179 requireContext()); 180 } 181 liveData.resetState(); 182 break; 183 case ManageRoleHolderStateLiveData.STATE_FAILURE: 184 liveData.resetState(); 185 break; 186 } 187 } 188 189 @Override onPreferenceClick(@onNull Preference preference)190 public boolean onPreferenceClick(@NonNull Preference preference) { 191 ApplicationInfo applicationInfo = preference.getExtras().getParcelable( 192 PREFERENCE_EXTRA_APPLICATION_INFO); 193 String packageName = applicationInfo.packageName; 194 UserHandle user = UserHandle.getUserHandleForUid(applicationInfo.uid); 195 boolean allow = !((TwoStatePreference) preference).isChecked(); 196 String key = preference.getKey(); 197 mViewModel.setSpecialAppAccessAsUser(packageName, allow, user, key, this, 198 this::onManageRoleHolderStateChanged); 199 return true; 200 } 201 202 @NonNull requirePreferenceFragment()203 private PF requirePreferenceFragment() { 204 //noinspection unchecked 205 return (PF) requireParentFragment(); 206 } 207 208 /** 209 * Interface that the parent fragment must implement. 210 */ 211 public interface Parent { 212 213 /** 214 * Set the title of the current settings page. 215 * 216 * @param title the title of the current settings page 217 */ setTitle(@onNull CharSequence title)218 void setTitle(@NonNull CharSequence title); 219 220 /** 221 * Create a new preference for an application. 222 * 223 * @param context the {@code Context} to use when creating the preference. 224 * 225 * @return a new preference for an application 226 */ 227 @NonNull createApplicationPreference(@onNull Context context)228 TwoStatePreference createApplicationPreference(@NonNull Context context); 229 230 /** 231 * Create a new preference for the footer. 232 * 233 * @param context the {@code Context} to use when creating the preference. 234 * 235 * @return a new preference for the footer 236 */ 237 @NonNull createFooterPreference(@onNull Context context)238 Preference createFooterPreference(@NonNull Context context); 239 240 /** 241 * Callback when changes have been made to the {@link PreferenceScreen} of the parent 242 * {@link PreferenceFragmentCompat}. 243 */ onPreferenceScreenChanged()244 void onPreferenceScreenChanged(); 245 } 246 } 247