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