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