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.settings.applications.specialaccess.deviceadmin; 18 19 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; 20 21 import android.app.AppGlobals; 22 import android.app.admin.DeviceAdminInfo; 23 import android.app.admin.DeviceAdminReceiver; 24 import android.app.admin.DevicePolicyManager; 25 import android.content.BroadcastReceiver; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.pm.ActivityInfo; 31 import android.content.pm.IPackageManager; 32 import android.content.pm.PackageManager; 33 import android.content.pm.ResolveInfo; 34 import android.os.RemoteException; 35 import android.os.UserHandle; 36 import android.os.UserManager; 37 import android.text.TextUtils; 38 import android.util.ArrayMap; 39 import android.util.Log; 40 import android.util.SparseArray; 41 42 import androidx.annotation.VisibleForTesting; 43 import androidx.preference.Preference; 44 import androidx.preference.PreferenceGroup; 45 import androidx.preference.PreferenceScreen; 46 import androidx.preference.SwitchPreference; 47 48 import com.android.settings.R; 49 import com.android.settings.core.BasePreferenceController; 50 import com.android.settings.widget.FilterTouchesSwitchPreference; 51 import com.android.settingslib.core.lifecycle.LifecycleObserver; 52 import com.android.settingslib.core.lifecycle.events.OnStart; 53 import com.android.settingslib.core.lifecycle.events.OnStop; 54 import com.android.settingslib.widget.FooterPreference; 55 import com.android.settingslib.widget.FooterPreferenceMixinCompat; 56 57 import org.xmlpull.v1.XmlPullParserException; 58 59 import java.io.IOException; 60 import java.util.ArrayList; 61 import java.util.Collection; 62 import java.util.Collections; 63 import java.util.List; 64 import java.util.Map; 65 66 public class DeviceAdminListPreferenceController extends BasePreferenceController 67 implements LifecycleObserver, OnStart, OnStop { 68 69 private static final IntentFilter FILTER = new IntentFilter(); 70 private static final String TAG = "DeviceAdminListPrefCtrl"; 71 72 private final DevicePolicyManager mDPM; 73 private final UserManager mUm; 74 private final PackageManager mPackageManager; 75 private final IPackageManager mIPackageManager; 76 /** 77 * Internal collection of device admin info objects for all profiles associated with the current 78 * user. 79 */ 80 private final ArrayList<DeviceAdminListItem> mAdmins = new ArrayList<>(); 81 private final SparseArray<ComponentName> mProfileOwnerComponents = new SparseArray<>(); 82 83 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 84 @Override 85 public void onReceive(Context context, Intent intent) { 86 // Refresh the list, if state change has been received. It could be that checkboxes 87 // need to be updated 88 if (TextUtils.equals(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED, intent.getAction())) { 89 updateList(); 90 } 91 } 92 }; 93 94 private PreferenceGroup mPreferenceGroup; 95 private FooterPreferenceMixinCompat mFooterPreferenceMixin; 96 97 static { 98 FILTER.addAction(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); 99 } 100 DeviceAdminListPreferenceController(Context context, String preferenceKey)101 public DeviceAdminListPreferenceController(Context context, String preferenceKey) { 102 super(context, preferenceKey); 103 mDPM = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); 104 mUm = (UserManager) context.getSystemService(Context.USER_SERVICE); 105 mPackageManager = mContext.getPackageManager(); 106 mIPackageManager = AppGlobals.getPackageManager(); 107 } 108 setFooterPreferenceMixin( FooterPreferenceMixinCompat mixin)109 public DeviceAdminListPreferenceController setFooterPreferenceMixin( 110 FooterPreferenceMixinCompat mixin) { 111 mFooterPreferenceMixin = mixin; 112 return this; 113 } 114 115 @Override getAvailabilityStatus()116 public int getAvailabilityStatus() { 117 return AVAILABLE; 118 } 119 120 @Override displayPreference(PreferenceScreen screen)121 public void displayPreference(PreferenceScreen screen) { 122 super.displayPreference(screen); 123 mPreferenceGroup = screen.findPreference(getPreferenceKey()); 124 } 125 126 @Override onStart()127 public void onStart() { 128 mContext.registerReceiverAsUser( 129 mBroadcastReceiver, UserHandle.ALL, FILTER, 130 null /* broadcastPermission */, null /* scheduler */); 131 } 132 133 @Override updateState(Preference preference)134 public void updateState(Preference preference) { 135 super.updateState(preference); 136 mProfileOwnerComponents.clear(); 137 final List<UserHandle> profiles = mUm.getUserProfiles(); 138 final int profilesSize = profiles.size(); 139 for (int i = 0; i < profilesSize; ++i) { 140 final int profileId = profiles.get(i).getIdentifier(); 141 mProfileOwnerComponents.put(profileId, mDPM.getProfileOwnerAsUser(profileId)); 142 } 143 updateList(); 144 } 145 146 @Override onStop()147 public void onStop() { 148 mContext.unregisterReceiver(mBroadcastReceiver); 149 } 150 151 @VisibleForTesting updateList()152 void updateList() { 153 refreshData(); 154 refreshUI(); 155 } 156 refreshData()157 private void refreshData() { 158 mAdmins.clear(); 159 final List<UserHandle> profiles = mUm.getUserProfiles(); 160 for (UserHandle profile : profiles) { 161 final int profileId = profile.getIdentifier(); 162 updateAvailableAdminsForProfile(profileId); 163 } 164 Collections.sort(mAdmins); 165 } 166 refreshUI()167 private void refreshUI() { 168 if (mPreferenceGroup == null) { 169 return; 170 } 171 if (mFooterPreferenceMixin != null) { 172 final FooterPreference footer = mFooterPreferenceMixin.createFooterPreference(); 173 footer.setTitle(R.string.no_device_admins); 174 footer.setVisible(mAdmins.isEmpty()); 175 } 176 final Map<String, FilterTouchesSwitchPreference> preferenceCache = new ArrayMap<>(); 177 final Context prefContext = mPreferenceGroup.getContext(); 178 final int childrenCount = mPreferenceGroup.getPreferenceCount(); 179 for (int i = 0; i < childrenCount; i++) { 180 final Preference pref = mPreferenceGroup.getPreference(i); 181 if (!(pref instanceof FilterTouchesSwitchPreference)) { 182 continue; 183 } 184 final FilterTouchesSwitchPreference appSwitch = (FilterTouchesSwitchPreference) pref; 185 preferenceCache.put(appSwitch.getKey(), appSwitch); 186 } 187 for (DeviceAdminListItem item : mAdmins) { 188 final String key = item.getKey(); 189 FilterTouchesSwitchPreference pref = preferenceCache.remove(key); 190 if (pref == null) { 191 pref = new FilterTouchesSwitchPreference(prefContext); 192 mPreferenceGroup.addPreference(pref); 193 } 194 bindPreference(item, pref); 195 } 196 for (FilterTouchesSwitchPreference unusedCacheItem : preferenceCache.values()) { 197 mPreferenceGroup.removePreference(unusedCacheItem); 198 } 199 } 200 bindPreference(DeviceAdminListItem item, FilterTouchesSwitchPreference pref)201 private void bindPreference(DeviceAdminListItem item, FilterTouchesSwitchPreference pref) { 202 pref.setKey(item.getKey()); 203 pref.setTitle(item.getName()); 204 pref.setIcon(item.getIcon()); 205 pref.setChecked(item.isActive()); 206 pref.setSummary(item.getDescription()); 207 pref.setEnabled(item.isEnabled()); 208 pref.setOnPreferenceClickListener(preference -> { 209 final UserHandle user = item.getUser(); 210 mContext.startActivityAsUser(item.getLaunchIntent(mContext), user); 211 return true; 212 }); 213 pref.setOnPreferenceChangeListener((preference, newValue) -> false); 214 } 215 216 /** 217 * Add device admins to the internal collection that belong to a profile. 218 * 219 * @param profileId the profile identifier. 220 */ updateAvailableAdminsForProfile(final int profileId)221 private void updateAvailableAdminsForProfile(final int profileId) { 222 // We are adding the union of two sets 'A' and 'B' of device admins to mAvailableAdmins. 223 // - Set 'A' is the set of active admins for the profile 224 // - set 'B' is the set of listeners to DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED for 225 // the profile. 226 227 // Add all of set 'A' to mAvailableAdmins. 228 final List<ComponentName> activeAdminsForProfile = mDPM.getActiveAdminsAsUser(profileId); 229 addActiveAdminsForProfile(activeAdminsForProfile, profileId); 230 231 // Collect set 'B' and add B-A to mAvailableAdmins. 232 addDeviceAdminBroadcastReceiversForProfile(activeAdminsForProfile, profileId); 233 } 234 235 /** 236 * Add a {@link DeviceAdminInfo} object to the internal collection of available admins for all 237 * active admin components associated with a profile. 238 */ addActiveAdminsForProfile(List<ComponentName> activeAdmins, int profileId)239 private void addActiveAdminsForProfile(List<ComponentName> activeAdmins, int profileId) { 240 if (activeAdmins == null) { 241 return; 242 } 243 244 for (ComponentName activeAdmin : activeAdmins) { 245 final ActivityInfo ai; 246 try { 247 ai = mIPackageManager.getReceiverInfo(activeAdmin, 248 PackageManager.GET_META_DATA | 249 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS | 250 PackageManager.MATCH_DIRECT_BOOT_UNAWARE | 251 PackageManager.MATCH_DIRECT_BOOT_AWARE, profileId); 252 } catch (RemoteException e) { 253 Log.w(TAG, "Unable to load component: " + activeAdmin); 254 continue; 255 } 256 final DeviceAdminInfo deviceAdminInfo = createDeviceAdminInfo(mContext, ai); 257 if (deviceAdminInfo == null) { 258 continue; 259 } 260 mAdmins.add(new DeviceAdminListItem(mContext, deviceAdminInfo)); 261 } 262 } 263 264 /** 265 * Add a profile's device admins that are receivers of 266 * {@code DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED} to the internal collection if they 267 * haven't been added yet. 268 * 269 * @param alreadyAddedComponents the set of active admin component names. Receivers of 270 * {@code DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED} 271 * whose component is in this 272 * set are not added to the internal collection again. 273 * @param profileId the identifier of the profile 274 */ addDeviceAdminBroadcastReceiversForProfile( Collection<ComponentName> alreadyAddedComponents, int profileId)275 private void addDeviceAdminBroadcastReceiversForProfile( 276 Collection<ComponentName> alreadyAddedComponents, int profileId) { 277 final List<ResolveInfo> enabledForProfile = mPackageManager.queryBroadcastReceiversAsUser( 278 new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED), 279 PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS, 280 profileId); 281 if (enabledForProfile == null) { 282 return; 283 } 284 for (ResolveInfo resolveInfo : enabledForProfile) { 285 final ComponentName riComponentName = 286 new ComponentName(resolveInfo.activityInfo.packageName, 287 resolveInfo.activityInfo.name); 288 if (alreadyAddedComponents != null 289 && alreadyAddedComponents.contains(riComponentName)) { 290 continue; 291 } 292 DeviceAdminInfo deviceAdminInfo = createDeviceAdminInfo( 293 mContext, resolveInfo.activityInfo); 294 // add only visible ones (note: active admins are added regardless of visibility) 295 if (deviceAdminInfo != null && deviceAdminInfo.isVisible()) { 296 if (!deviceAdminInfo.getActivityInfo().applicationInfo.isInternal()) { 297 continue; 298 } 299 mAdmins.add(new DeviceAdminListItem(mContext, deviceAdminInfo)); 300 } 301 } 302 } 303 304 /** 305 * Creates a device admin info object for the resolved intent that points to the component of 306 * the device admin. 307 * 308 * @param ai ActivityInfo for the admin component. 309 * @return new {@link DeviceAdminInfo} object or null if there was an error. 310 */ createDeviceAdminInfo(Context context, ActivityInfo ai)311 private static DeviceAdminInfo createDeviceAdminInfo(Context context, ActivityInfo ai) { 312 try { 313 return new DeviceAdminInfo(context, ai); 314 } catch (XmlPullParserException | IOException e) { 315 Log.w(TAG, "Skipping " + ai, e); 316 } 317 return null; 318 } 319 } 320