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