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