1/*
2 * Copyright (C) 2010 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
17package com.android.settings;
18
19import android.app.Activity;
20import android.app.AppGlobals;
21import android.app.ListFragment;
22import android.app.admin.DeviceAdminInfo;
23import android.app.admin.DeviceAdminReceiver;
24import android.app.admin.DevicePolicyManager;
25import android.content.BroadcastReceiver;
26import android.content.ComponentName;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.pm.ActivityInfo;
31import android.content.pm.IPackageManager;
32import android.content.pm.PackageManager;
33import android.content.pm.ResolveInfo;
34import android.content.res.Resources;
35import android.graphics.drawable.Drawable;
36import android.os.Bundle;
37import android.os.RemoteException;
38import android.os.UserHandle;
39import android.os.UserManager;
40import android.util.Log;
41import android.util.SparseArray;
42import android.view.LayoutInflater;
43import android.view.View;
44import android.view.ViewGroup;
45import android.widget.BaseAdapter;
46import android.widget.CheckBox;
47import android.widget.ImageView;
48import android.widget.ListView;
49import android.widget.TextView;
50
51import com.android.internal.logging.nano.MetricsProto;
52import com.android.settings.core.instrumentation.Instrumentable;
53import com.android.settings.core.instrumentation.VisibilityLoggerMixin;
54
55import org.xmlpull.v1.XmlPullParserException;
56
57import java.io.IOException;
58import java.util.ArrayList;
59import java.util.Collection;
60import java.util.Collections;
61import java.util.List;
62
63public class DeviceAdminSettings extends ListFragment implements Instrumentable {
64    static final String TAG = "DeviceAdminSettings";
65
66    private final VisibilityLoggerMixin mVisibilityLoggerMixin =
67            new VisibilityLoggerMixin(getMetricsCategory());
68    private DevicePolicyManager mDPM;
69    private UserManager mUm;
70
71    private static class DeviceAdminListItem implements Comparable<DeviceAdminListItem> {
72        public DeviceAdminInfo info;
73
74        // These aren't updated so they keep a stable sort order if user activates / de-activates
75        // an admin.
76        public String name;
77        public boolean active;
78
79        public int compareTo(DeviceAdminListItem other)  {
80            // Sort active admins first, then by name.
81            if (this.active != other.active) {
82                return this.active ? -1 : 1;
83            }
84            return this.name.compareTo(other.name);
85        }
86    }
87
88    @Override
89    public void onAttach(Context context) {
90        super.onAttach(context);
91        mVisibilityLoggerMixin.onAttach(context);
92    }
93
94    /**
95     * Internal collection of device admin info objects for all profiles associated with the current
96     * user.
97     */
98    private final ArrayList<DeviceAdminListItem>
99            mAdmins = new ArrayList<DeviceAdminListItem>();
100
101    private String mDeviceOwnerPkg;
102    private SparseArray<ComponentName> mProfileOwnerComponents = new SparseArray<ComponentName>();
103
104    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
105        @Override
106        public void onReceive(Context context, Intent intent) {
107            // Refresh the list, if state change has been received. It could be that checkboxes
108            // need to be updated
109            if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(
110                    intent.getAction())) {
111                updateList();
112            }
113        }
114    };
115
116    @Override
117    public int getMetricsCategory() {
118        return MetricsProto.MetricsEvent.DEVICE_ADMIN_SETTINGS;
119    }
120
121    @Override
122    public void onCreate(Bundle icicle) {
123        super.onCreate(icicle);
124    }
125
126    @Override
127    public View onCreateView(LayoutInflater inflater, ViewGroup container,
128            Bundle savedInstanceState) {
129        mDPM = (DevicePolicyManager) getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
130        mUm = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
131        return inflater.inflate(R.layout.device_admin_settings, container, false);
132    }
133
134    @Override
135    public void onActivityCreated(Bundle savedInstanceState) {
136        super.onActivityCreated(savedInstanceState);
137        setHasOptionsMenu(true);
138        Utils.forceCustomPadding(getListView(), true /* additive padding */);
139    }
140
141    @Override
142    public void onResume() {
143        super.onResume();
144        final Activity activity = getActivity();
145        mVisibilityLoggerMixin.onResume();
146        IntentFilter filter = new IntentFilter();
147        filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
148        activity.registerReceiverAsUser(
149                mBroadcastReceiver, UserHandle.ALL, filter, null, null);
150
151        final ComponentName deviceOwnerComponent = mDPM.getDeviceOwnerComponentOnAnyUser();
152        mDeviceOwnerPkg =
153                deviceOwnerComponent != null ? deviceOwnerComponent.getPackageName() : null;
154        mProfileOwnerComponents.clear();
155        final List<UserHandle> profiles = mUm.getUserProfiles();
156        final int profilesSize = profiles.size();
157        for (int i = 0; i < profilesSize; ++i) {
158            final int profileId = profiles.get(i).getIdentifier();
159            mProfileOwnerComponents.put(profileId, mDPM.getProfileOwnerAsUser(profileId));
160        }
161        updateList();
162    }
163
164    @Override
165    public void onPause() {
166        final Activity activity = getActivity();
167        activity.unregisterReceiver(mBroadcastReceiver);
168        mVisibilityLoggerMixin.onPause();
169        super.onPause();
170    }
171
172    /**
173     * Update the internal collection of available admins for all profiles associated with the
174     * current user.
175     */
176    void updateList() {
177        mAdmins.clear();
178
179        final List<UserHandle> profiles = mUm.getUserProfiles();
180        final int profilesSize = profiles.size();
181        for (int i = 0; i < profilesSize; ++i) {
182            final int profileId = profiles.get(i).getIdentifier();
183            updateAvailableAdminsForProfile(profileId);
184        }
185        Collections.sort(mAdmins);
186
187        getListView().setAdapter(new PolicyListAdapter());
188    }
189
190    @Override
191    public void onListItemClick(ListView l, View v, int position, long id) {
192        Object o = l.getAdapter().getItem(position);
193        DeviceAdminInfo dpi = (DeviceAdminInfo) o;
194        final UserHandle user = new UserHandle(getUserId(dpi));
195        final Activity activity = getActivity();
196        Intent intent = new Intent(activity, DeviceAdminAdd.class);
197        intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, dpi.getComponent());
198        activity.startActivityAsUser(intent, user);
199    }
200
201    static class ViewHolder {
202        ImageView icon;
203        TextView name;
204        CheckBox checkbox;
205        TextView description;
206    }
207
208    class PolicyListAdapter extends BaseAdapter {
209        final LayoutInflater mInflater;
210
211        PolicyListAdapter() {
212            mInflater = (LayoutInflater)
213                    getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
214        }
215
216        @Override
217        public boolean hasStableIds() {
218            return false;
219        }
220
221        @Override
222        public int getCount() {
223            return mAdmins.size();
224        }
225
226        /**
227         * The item for the given position in the list.
228         *
229         * @return DeviceAdminInfo object for actual device admins.
230         */
231        @Override
232        public Object getItem(int position) {
233            return ((DeviceAdminListItem) (mAdmins.get(position))).info;
234        }
235
236        @Override
237        public long getItemId(int position) {
238            return position;
239        }
240
241        @Override
242        public boolean areAllItemsEnabled() {
243            return false;
244        }
245
246        /**
247         * See {@link #getItemViewType} for the view types.
248         */
249        @Override
250        public int getViewTypeCount() {
251            return 1;
252        }
253
254        /**
255         * Returns 0 for all types.
256         */
257        @Override
258        public int getItemViewType(int position) {
259            return 0;
260        }
261
262        @Override
263        public boolean isEnabled(int position) {
264            Object o = getItem(position);
265            return isEnabled(o);
266        }
267
268        private boolean isEnabled(Object o) {
269            DeviceAdminInfo info = (DeviceAdminInfo) o;
270            // Disable item if admin is being removed
271            if (isRemovingAdmin(info)) {
272                return false;
273            }
274            return true;
275        }
276
277        @Override
278        public View getView(int position, View convertView, ViewGroup parent) {
279            Object o = getItem(position);
280            if (convertView == null) {
281                convertView = newDeviceAdminView(parent);
282            }
283            bindView(convertView, (DeviceAdminInfo) o);
284            return convertView;
285        }
286
287        private View newDeviceAdminView(ViewGroup parent) {
288            View v = mInflater.inflate(R.layout.device_admin_item, parent, false);
289            ViewHolder h = new ViewHolder();
290            h.icon = (ImageView) v.findViewById(R.id.icon);
291            h.name = (TextView) v.findViewById(R.id.name);
292            h.checkbox = (CheckBox) v.findViewById(R.id.checkbox);
293            h.description = (TextView) v.findViewById(R.id.description);
294            v.setTag(h);
295            return v;
296        }
297
298        private void bindView(View view, DeviceAdminInfo item) {
299            final Activity activity = getActivity();
300            ViewHolder vh = (ViewHolder) view.getTag();
301            Drawable activityIcon = item.loadIcon(activity.getPackageManager());
302            Drawable badgedIcon = activity.getPackageManager().getUserBadgedIcon(
303                    activityIcon, new UserHandle(getUserId(item)));
304            vh.icon.setImageDrawable(badgedIcon);
305            vh.name.setText(item.loadLabel(activity.getPackageManager()));
306            vh.checkbox.setChecked(isActiveAdmin(item));
307            final boolean enabled = isEnabled(item);
308            try {
309                vh.description.setText(item.loadDescription(activity.getPackageManager()));
310            } catch (Resources.NotFoundException e) {
311            }
312            vh.checkbox.setEnabled(enabled);
313            vh.name.setEnabled(enabled);
314            vh.description.setEnabled(enabled);
315            vh.icon.setEnabled(enabled);
316        }
317    }
318
319    private boolean isDeviceOwner(DeviceAdminInfo item) {
320        return getUserId(item) == UserHandle.myUserId()
321                && item.getPackageName().equals(mDeviceOwnerPkg);
322    }
323
324    private boolean isProfileOwner(DeviceAdminInfo item) {
325        ComponentName profileOwner = mProfileOwnerComponents.get(getUserId(item));
326        return item.getComponent().equals(profileOwner);
327    }
328
329    private boolean isActiveAdmin(DeviceAdminInfo item) {
330        return mDPM.isAdminActiveAsUser(item.getComponent(), getUserId(item));
331    }
332
333    private boolean isRemovingAdmin(DeviceAdminInfo item) {
334        return mDPM.isRemovingAdmin(item.getComponent(), getUserId(item));
335    }
336
337    /**
338     * Add device admins to the internal collection that belong to a profile.
339     *
340     * @param profileId the profile identifier.
341     */
342    private void updateAvailableAdminsForProfile(final int profileId) {
343        // We are adding the union of two sets 'A' and 'B' of device admins to mAvailableAdmins.
344        // Set 'A' is the set of active admins for the profile whereas set 'B' is the set of
345        // listeners to DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED for the profile.
346
347        // Add all of set 'A' to mAvailableAdmins.
348        List<ComponentName> activeAdminsListForProfile = mDPM.getActiveAdminsAsUser(profileId);
349        addActiveAdminsForProfile(activeAdminsListForProfile, profileId);
350
351        // Collect set 'B' and add B-A to mAvailableAdmins.
352        addDeviceAdminBroadcastReceiversForProfile(activeAdminsListForProfile, profileId);
353    }
354
355    /**
356     * Add a profile's device admins that are receivers of
357     * {@code DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED} to the internal collection if they
358     * haven't been added yet.
359     *
360     * @param alreadyAddedComponents the set of active admin component names. Receivers of
361     *            {@code DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED} whose component is in this
362     *            set are not added to the internal collection again.
363     * @param profileId the identifier of the profile
364     */
365    private void addDeviceAdminBroadcastReceiversForProfile(
366            Collection<ComponentName> alreadyAddedComponents, final int profileId) {
367        final PackageManager pm = getActivity().getPackageManager();
368        List<ResolveInfo> enabledForProfile = pm.queryBroadcastReceiversAsUser(
369                new Intent(DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED),
370                PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
371                profileId);
372        if (enabledForProfile == null) {
373            enabledForProfile = Collections.emptyList();
374        }
375        final int n = enabledForProfile.size();
376        for (int i = 0; i < n; ++i) {
377            ResolveInfo resolveInfo = enabledForProfile.get(i);
378            ComponentName riComponentName =
379                    new ComponentName(resolveInfo.activityInfo.packageName,
380                            resolveInfo.activityInfo.name);
381            if (alreadyAddedComponents == null
382                    || !alreadyAddedComponents.contains(riComponentName)) {
383                DeviceAdminInfo deviceAdminInfo =  createDeviceAdminInfo(resolveInfo.activityInfo);
384                // add only visible ones (note: active admins are added regardless of visibility)
385                if (deviceAdminInfo != null && deviceAdminInfo.isVisible()) {
386                    if (!deviceAdminInfo.getActivityInfo().applicationInfo.isInternal()) {
387                        continue;
388                    }
389                    DeviceAdminListItem item = new DeviceAdminListItem();
390                    item.info = deviceAdminInfo;
391                    item.name = deviceAdminInfo.loadLabel(pm).toString();
392                    // Active ones already added.
393                    item.active = false;
394                    mAdmins.add(item);
395                }
396            }
397        }
398    }
399
400    /**
401     * Add a {@link DeviceAdminInfo} object to the internal collection of available admins for all
402     * active admin components associated with a profile.
403     *
404     * @param profileId a profile identifier.
405     */
406    private void addActiveAdminsForProfile(final List<ComponentName> activeAdmins,
407            final int profileId) {
408        if (activeAdmins != null) {
409            final PackageManager packageManager = getActivity().getPackageManager();
410            final IPackageManager iPackageManager = AppGlobals.getPackageManager();
411            final int n = activeAdmins.size();
412            for (int i = 0; i < n; ++i) {
413                final ComponentName activeAdmin = activeAdmins.get(i);
414                final ActivityInfo ai;
415                try {
416                    ai = iPackageManager.getReceiverInfo(activeAdmin,
417                            PackageManager.GET_META_DATA |
418                            PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS |
419                            PackageManager.MATCH_DIRECT_BOOT_UNAWARE |
420                            PackageManager.MATCH_DIRECT_BOOT_AWARE, profileId);
421                } catch (RemoteException e) {
422                    Log.w(TAG, "Unable to load component: " + activeAdmin);
423                    continue;
424                }
425                final DeviceAdminInfo deviceAdminInfo = createDeviceAdminInfo(ai);
426                if (deviceAdminInfo == null) {
427                    continue;
428                }
429                // Don't do the applicationInfo.isInternal() check here; if an active
430                // admin is already on SD card, just show it.
431                final DeviceAdminListItem item = new DeviceAdminListItem();
432                item.info = deviceAdminInfo;
433                item.name = deviceAdminInfo.loadLabel(packageManager).toString();
434                item.active = true;
435                mAdmins.add(item);
436            }
437        }
438    }
439
440    /**
441     * Creates a device admin info object for the resolved intent that points to the component of
442     * the device admin.
443     *
444     * @param ai ActivityInfo for the admin component.
445     * @return new {@link DeviceAdminInfo} object or null if there was an error.
446     */
447    private DeviceAdminInfo createDeviceAdminInfo(ActivityInfo ai) {
448        try {
449            return new DeviceAdminInfo(getActivity(), ai);
450        } catch (XmlPullParserException|IOException e) {
451            Log.w(TAG, "Skipping " + ai, e);
452        }
453        return null;
454    }
455
456    /**
457     * Extracts the user id from a device admin info object.
458     * @param adminInfo the device administrator info.
459     * @return identifier of the user associated with the device admin.
460     */
461    private int getUserId(DeviceAdminInfo adminInfo) {
462        return UserHandle.getUserId(adminInfo.getActivityInfo().applicationInfo.uid);
463    }
464}
465