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