• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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