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