• Home
  • 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.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