AppSecurityPermissions.java revision 0e128bb2e03dafdabc06710a6b0ea93f0e62a188
1/*
2**
3** Copyright 2007, The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9**     http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17package android.widget;
18
19import com.android.internal.R;
20
21import android.app.AlertDialog;
22import android.content.Context;
23import android.content.pm.ApplicationInfo;
24import android.content.pm.PackageInfo;
25import android.content.pm.PackageManager;
26import android.content.pm.PackageManager.NameNotFoundException;
27import android.content.pm.PackageParser;
28import android.content.pm.PermissionGroupInfo;
29import android.content.pm.PermissionInfo;
30import android.graphics.drawable.Drawable;
31import android.os.Parcel;
32import android.text.SpannableStringBuilder;
33import android.text.TextUtils;
34import android.util.AttributeSet;
35import android.util.Log;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.ViewGroup;
39
40import java.text.Collator;
41import java.util.ArrayList;
42import java.util.Collections;
43import java.util.Comparator;
44import java.util.HashMap;
45import java.util.HashSet;
46import java.util.List;
47import java.util.Map;
48import java.util.Set;
49
50/**
51 * This class contains the SecurityPermissions view implementation.
52 * Initially the package's advanced or dangerous security permissions
53 * are displayed under categorized
54 * groups. Clicking on the additional permissions presents
55 * extended information consisting of all groups and permissions.
56 * To use this view define a LinearLayout or any ViewGroup and add this
57 * view by instantiating AppSecurityPermissions and invoking getPermissionsView.
58 *
59 * {@hide}
60 */
61public class AppSecurityPermissions {
62
63    public static final int WHICH_PERSONAL = 1<<0;
64    public static final int WHICH_DEVICE = 1<<1;
65    public static final int WHICH_NEW = 1<<2;
66    public static final int WHICH_ALL = 0xffff;
67
68    private final static String TAG = "AppSecurityPermissions";
69    private boolean localLOGV = false;
70    private Context mContext;
71    private LayoutInflater mInflater;
72    private PackageManager mPm;
73    private PackageInfo mInstalledPackageInfo;
74    private final Map<String, MyPermissionGroupInfo> mPermGroups
75            = new HashMap<String, MyPermissionGroupInfo>();
76    private final List<MyPermissionGroupInfo> mPermGroupsList
77            = new ArrayList<MyPermissionGroupInfo>();
78    private final PermissionGroupInfoComparator mPermGroupComparator;
79    private final PermissionInfoComparator mPermComparator;
80    private List<MyPermissionInfo> mPermsList;
81    private CharSequence mNewPermPrefix;
82    private Drawable mNormalIcon;
83    private Drawable mDangerousIcon;
84
85    static class MyPermissionGroupInfo extends PermissionGroupInfo {
86        CharSequence mLabel;
87
88        final ArrayList<MyPermissionInfo> mNewPermissions = new ArrayList<MyPermissionInfo>();
89        final ArrayList<MyPermissionInfo> mPersonalPermissions = new ArrayList<MyPermissionInfo>();
90        final ArrayList<MyPermissionInfo> mDevicePermissions = new ArrayList<MyPermissionInfo>();
91        final ArrayList<MyPermissionInfo> mAllPermissions = new ArrayList<MyPermissionInfo>();
92
93        MyPermissionGroupInfo(PermissionInfo perm) {
94            name = perm.packageName;
95            packageName = perm.packageName;
96        }
97
98        MyPermissionGroupInfo(PermissionGroupInfo info) {
99            super(info);
100        }
101    }
102
103    static class MyPermissionInfo extends PermissionInfo {
104        CharSequence mLabel;
105
106        /**
107         * PackageInfo.requestedPermissionsFlags for the new package being installed.
108         */
109        int mNewReqFlags;
110
111        /**
112         * PackageInfo.requestedPermissionsFlags for the currently installed
113         * package, if it is installed.
114         */
115        int mExistingReqFlags;
116
117        /**
118         * True if this should be considered a new permission.
119         */
120        boolean mNew;
121
122        MyPermissionInfo() {
123        }
124
125        MyPermissionInfo(PermissionInfo info) {
126            super(info);
127        }
128
129        MyPermissionInfo(MyPermissionInfo info) {
130            super(info);
131            mNewReqFlags = info.mNewReqFlags;
132            mExistingReqFlags = info.mExistingReqFlags;
133            mNew = info.mNew;
134        }
135    }
136
137    public static class PermissionItemView extends LinearLayout implements View.OnClickListener {
138        MyPermissionGroupInfo mGroup;
139        MyPermissionInfo mPerm;
140        AlertDialog mDialog;
141
142        public PermissionItemView(Context context, AttributeSet attrs) {
143            super(context, attrs);
144            setClickable(true);
145        }
146
147        public void setPermission(MyPermissionGroupInfo grp, MyPermissionInfo perm,
148                boolean first, CharSequence newPermPrefix) {
149            mGroup = grp;
150            mPerm = perm;
151
152            ImageView permGrpIcon = (ImageView) findViewById(R.id.perm_icon);
153            TextView permNameView = (TextView) findViewById(R.id.perm_name);
154
155            PackageManager pm = getContext().getPackageManager();
156            Drawable icon = null;
157            if (first) {
158                if (grp.icon != 0) {
159                    icon = grp.loadIcon(pm);
160                } else {
161                    ApplicationInfo appInfo;
162                    try {
163                        appInfo = pm.getApplicationInfo(grp.packageName, 0);
164                        icon = appInfo.loadIcon(pm);
165                    } catch (NameNotFoundException e) {
166                    }
167                }
168            }
169            CharSequence label = perm.mLabel;
170            if (perm.mNew && newPermPrefix != null) {
171                // If this is a new permission, format it appropriately.
172                SpannableStringBuilder builder = new SpannableStringBuilder();
173                Parcel parcel = Parcel.obtain();
174                TextUtils.writeToParcel(newPermPrefix, parcel, 0);
175                parcel.setDataPosition(0);
176                CharSequence newStr = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
177                parcel.recycle();
178                builder.append(newStr);
179                builder.append(label);
180                label = builder;
181            }
182
183            permGrpIcon.setImageDrawable(icon);
184            permNameView.setText(label);
185            setOnClickListener(this);
186        }
187
188        @Override
189        public void onClick(View v) {
190            if (mGroup != null && mPerm != null) {
191                if (mDialog != null) {
192                    mDialog.dismiss();
193                }
194                AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
195                builder.setTitle(mGroup.mLabel);
196                builder.setMessage(mPerm.loadDescription(getContext().getPackageManager()));
197                builder.setCancelable(true);
198                mDialog = builder.show();
199                mDialog.setCanceledOnTouchOutside(true);
200            }
201        }
202
203        @Override
204        protected void onDetachedFromWindow() {
205            super.onDetachedFromWindow();
206            if (mDialog != null) {
207                mDialog.dismiss();
208            }
209        }
210    }
211
212    public AppSecurityPermissions(Context context, List<PermissionInfo> permList) {
213        mContext = context;
214        mPm = mContext.getPackageManager();
215        loadResources();
216        mPermComparator = new PermissionInfoComparator();
217        mPermGroupComparator = new PermissionGroupInfoComparator();
218        for (PermissionInfo pi : permList) {
219            mPermsList.add(new MyPermissionInfo(pi));
220        }
221        setPermissions(mPermsList);
222    }
223
224    public AppSecurityPermissions(Context context, String packageName) {
225        mContext = context;
226        mPm = mContext.getPackageManager();
227        loadResources();
228        mPermComparator = new PermissionInfoComparator();
229        mPermGroupComparator = new PermissionGroupInfoComparator();
230        mPermsList = new ArrayList<MyPermissionInfo>();
231        Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>();
232        PackageInfo pkgInfo;
233        try {
234            pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
235        } catch (NameNotFoundException e) {
236            Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName);
237            return;
238        }
239        // Extract all user permissions
240        if((pkgInfo.applicationInfo != null) && (pkgInfo.applicationInfo.uid != -1)) {
241            getAllUsedPermissions(pkgInfo.applicationInfo.uid, permSet);
242        }
243        for(MyPermissionInfo tmpInfo : permSet) {
244            mPermsList.add(tmpInfo);
245        }
246        setPermissions(mPermsList);
247    }
248
249    public AppSecurityPermissions(Context context, PackageParser.Package pkg) {
250        mContext = context;
251        mPm = mContext.getPackageManager();
252        loadResources();
253        mPermComparator = new PermissionInfoComparator();
254        mPermGroupComparator = new PermissionGroupInfoComparator();
255        mPermsList = new ArrayList<MyPermissionInfo>();
256        Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>();
257        if(pkg == null) {
258            return;
259        }
260
261        // Convert to a PackageInfo
262        PackageInfo info = PackageParser.generatePackageInfo(pkg, null,
263                PackageManager.GET_PERMISSIONS, 0, 0, null);
264        PackageInfo installedPkgInfo = null;
265        // Get requested permissions
266        if (info.requestedPermissions != null) {
267            try {
268                installedPkgInfo = mPm.getPackageInfo(info.packageName,
269                        PackageManager.GET_PERMISSIONS);
270            } catch (NameNotFoundException e) {
271            }
272            extractPerms(info, permSet, installedPkgInfo);
273        }
274        // Get permissions related to  shared user if any
275        if (pkg.mSharedUserId != null) {
276            int sharedUid;
277            try {
278                sharedUid = mPm.getUidForSharedUser(pkg.mSharedUserId);
279                getAllUsedPermissions(sharedUid, permSet);
280            } catch (NameNotFoundException e) {
281                Log.w(TAG, "Could'nt retrieve shared user id for:"+pkg.packageName);
282            }
283        }
284        // Retrieve list of permissions
285        for (MyPermissionInfo tmpInfo : permSet) {
286            mPermsList.add(tmpInfo);
287        }
288        setPermissions(mPermsList);
289    }
290
291    private void loadResources() {
292        // Pick up from framework resources instead.
293        mNewPermPrefix = mContext.getText(R.string.perms_new_perm_prefix);
294        mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot);
295        mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission);
296    }
297
298    /**
299     * Utility to retrieve a view displaying a single permission.  This provides
300     * the old UI layout for permissions; it is only here for the device admin
301     * settings to continue to use.
302     */
303    public static View getPermissionItemView(Context context,
304            CharSequence grpName, CharSequence description, boolean dangerous) {
305        LayoutInflater inflater = (LayoutInflater)context.getSystemService(
306                Context.LAYOUT_INFLATER_SERVICE);
307        Drawable icon = context.getResources().getDrawable(dangerous
308                ? R.drawable.ic_bullet_key_permission : R.drawable.ic_text_dot);
309        return getPermissionItemViewOld(context, inflater, grpName,
310                description, dangerous, icon);
311    }
312
313    public PackageInfo getInstalledPackageInfo() {
314        return mInstalledPackageInfo;
315    }
316
317    private void getAllUsedPermissions(int sharedUid, Set<MyPermissionInfo> permSet) {
318        String sharedPkgList[] = mPm.getPackagesForUid(sharedUid);
319        if(sharedPkgList == null || (sharedPkgList.length == 0)) {
320            return;
321        }
322        for(String sharedPkg : sharedPkgList) {
323            getPermissionsForPackage(sharedPkg, permSet);
324        }
325    }
326
327    private void getPermissionsForPackage(String packageName,
328            Set<MyPermissionInfo> permSet) {
329        PackageInfo pkgInfo;
330        try {
331            pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
332        } catch (NameNotFoundException e) {
333            Log.w(TAG, "Couldn't retrieve permissions for package:"+packageName);
334            return;
335        }
336        if ((pkgInfo != null) && (pkgInfo.requestedPermissions != null)) {
337            extractPerms(pkgInfo, permSet, pkgInfo);
338        }
339    }
340
341    private void extractPerms(PackageInfo info, Set<MyPermissionInfo> permSet,
342            PackageInfo installedPkgInfo) {
343        String[] strList = info.requestedPermissions;
344        int[] flagsList = info.requestedPermissionsFlags;
345        if ((strList == null) || (strList.length == 0)) {
346            return;
347        }
348        mInstalledPackageInfo = installedPkgInfo;
349        for (int i=0; i<strList.length; i++) {
350            String permName = strList[i];
351            // If we are only looking at an existing app, then we only
352            // care about permissions that have actually been granted to it.
353            if (installedPkgInfo != null && info == installedPkgInfo) {
354                if ((flagsList[i]&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) {
355                    continue;
356                }
357            }
358            try {
359                PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0);
360                if (tmpPermInfo == null) {
361                    continue;
362                }
363                int existingIndex = -1;
364                if (installedPkgInfo != null
365                        && installedPkgInfo.requestedPermissions != null) {
366                    for (int j=0; j<installedPkgInfo.requestedPermissions.length; j++) {
367                        if (permName.equals(installedPkgInfo.requestedPermissions[j])) {
368                            existingIndex = j;
369                            break;
370                        }
371                    }
372                }
373                final int existingFlags = existingIndex >= 0 ?
374                        installedPkgInfo.requestedPermissionsFlags[existingIndex] : 0;
375                if (!isDisplayablePermission(tmpPermInfo, flagsList[i], existingFlags)) {
376                    // This is not a permission that is interesting for the user
377                    // to see, so skip it.
378                    continue;
379                }
380                final String origGroupName = tmpPermInfo.group;
381                String groupName = origGroupName;
382                if (groupName == null) {
383                    groupName = tmpPermInfo.packageName;
384                    tmpPermInfo.group = groupName;
385                }
386                MyPermissionGroupInfo group = mPermGroups.get(groupName);
387                if (group == null) {
388                    PermissionGroupInfo grp = null;
389                    if (origGroupName != null) {
390                        grp = mPm.getPermissionGroupInfo(origGroupName, 0);
391                    }
392                    if (grp != null) {
393                        group = new MyPermissionGroupInfo(grp);
394                    } else {
395                        // We could be here either because the permission
396                        // didn't originally specify a group or the group it
397                        // gave couldn't be found.  In either case, we consider
398                        // its group to be the permission's package name.
399                        tmpPermInfo.group = tmpPermInfo.packageName;
400                        group = mPermGroups.get(tmpPermInfo.group);
401                        if (group == null) {
402                            group = new MyPermissionGroupInfo(tmpPermInfo);
403                        }
404                        group = new MyPermissionGroupInfo(tmpPermInfo);
405                    }
406                    mPermGroups.put(tmpPermInfo.group, group);
407                }
408                final boolean newPerm = installedPkgInfo != null
409                        && (existingFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0;
410                MyPermissionInfo myPerm = new MyPermissionInfo(tmpPermInfo);
411                myPerm.mNewReqFlags = flagsList[i];
412                myPerm.mExistingReqFlags = existingFlags;
413                // This is a new permission if the app is already installed and
414                // doesn't currently hold this permission.
415                myPerm.mNew = newPerm;
416                permSet.add(myPerm);
417            } catch (NameNotFoundException e) {
418                Log.i(TAG, "Ignoring unknown permission:"+permName);
419            }
420        }
421    }
422
423    public int getPermissionCount() {
424        return getPermissionCount(WHICH_ALL);
425    }
426
427    private List<MyPermissionInfo> getPermissionList(MyPermissionGroupInfo grp, int which) {
428        if (which == WHICH_NEW) {
429            return grp.mNewPermissions;
430        } else if (which == WHICH_PERSONAL) {
431            return grp.mPersonalPermissions;
432        } else if (which == WHICH_DEVICE) {
433            return grp.mDevicePermissions;
434        } else {
435            return grp.mAllPermissions;
436        }
437    }
438
439    public int getPermissionCount(int which) {
440        int N = 0;
441        for (int i=0; i<mPermGroupsList.size(); i++) {
442            N += getPermissionList(mPermGroupsList.get(i), which).size();
443        }
444        return N;
445    }
446
447    public View getPermissionsView() {
448        return getPermissionsView(WHICH_ALL);
449    }
450
451    public View getPermissionsView(int which) {
452        mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
453
454        LinearLayout permsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null);
455        LinearLayout displayList = (LinearLayout) permsView.findViewById(R.id.perms_list);
456        View noPermsView = permsView.findViewById(R.id.no_permissions);
457
458        displayPermissions(mPermGroupsList, displayList, which);
459        if (displayList.getChildCount() <= 0) {
460            noPermsView.setVisibility(View.VISIBLE);
461        }
462
463        return permsView;
464    }
465
466    /**
467     * Utility method that displays permissions from a map containing group name and
468     * list of permission descriptions.
469     */
470    private void displayPermissions(List<MyPermissionGroupInfo> groups,
471            LinearLayout permListView, int which) {
472        permListView.removeAllViews();
473
474        int spacing = (int)(8*mContext.getResources().getDisplayMetrics().density);
475
476        for (int i=0; i<groups.size(); i++) {
477            MyPermissionGroupInfo grp = groups.get(i);
478            final List<MyPermissionInfo> perms = getPermissionList(grp, which);
479            for (int j=0; j<perms.size(); j++) {
480                MyPermissionInfo perm = perms.get(j);
481                View view = getPermissionItemView(grp, perm, j == 0,
482                        which != WHICH_NEW ? mNewPermPrefix : null);
483                LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
484                        ViewGroup.LayoutParams.MATCH_PARENT,
485                        ViewGroup.LayoutParams.WRAP_CONTENT);
486                if (j == 0) {
487                    lp.topMargin = spacing;
488                }
489                if (j == grp.mAllPermissions.size()-1) {
490                    lp.bottomMargin = spacing;
491                }
492                if (permListView.getChildCount() == 0) {
493                    lp.topMargin *= 2;
494                }
495                permListView.addView(view, lp);
496            }
497        }
498    }
499
500    private PermissionItemView getPermissionItemView(MyPermissionGroupInfo grp,
501            MyPermissionInfo perm, boolean first, CharSequence newPermPrefix) {
502        return getPermissionItemView(mContext, mInflater, grp, perm, first, newPermPrefix);
503    }
504
505    private static PermissionItemView getPermissionItemView(Context context, LayoutInflater inflater,
506            MyPermissionGroupInfo grp, MyPermissionInfo perm, boolean first,
507            CharSequence newPermPrefix) {
508        PermissionItemView permView = (PermissionItemView)inflater.inflate(
509                R.layout.app_permission_item, null);
510        permView.setPermission(grp, perm, first, newPermPrefix);
511        return permView;
512    }
513
514    private static View getPermissionItemViewOld(Context context, LayoutInflater inflater,
515            CharSequence grpName, CharSequence permList, boolean dangerous, Drawable icon) {
516        View permView = inflater.inflate(R.layout.app_permission_item_old, null);
517
518        TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group);
519        TextView permDescView = (TextView) permView.findViewById(R.id.permission_list);
520
521        ImageView imgView = (ImageView)permView.findViewById(R.id.perm_icon);
522        imgView.setImageDrawable(icon);
523        if(grpName != null) {
524            permGrpView.setText(grpName);
525            permDescView.setText(permList);
526        } else {
527            permGrpView.setText(permList);
528            permDescView.setVisibility(View.GONE);
529        }
530        return permView;
531    }
532
533    private boolean isDisplayablePermission(PermissionInfo pInfo, int newReqFlags,
534            int existingReqFlags) {
535        final int base = pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
536        // Dangerous and normal permissions are always shown to the user.
537        if (base == PermissionInfo.PROTECTION_DANGEROUS ||
538                base == PermissionInfo.PROTECTION_NORMAL) {
539            return true;
540        }
541        // Development permissions are only shown to the user if they are already
542        // granted to the app -- if we are installing an app and they are not
543        // already granted, they will not be granted as part of the install.
544        if ((existingReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0
545                && (pInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
546            return true;
547        }
548        return false;
549    }
550
551    private static class PermissionGroupInfoComparator implements Comparator<MyPermissionGroupInfo> {
552        private final Collator sCollator = Collator.getInstance();
553        PermissionGroupInfoComparator() {
554        }
555        public final int compare(MyPermissionGroupInfo a, MyPermissionGroupInfo b) {
556            if (((a.flags^b.flags)&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) {
557                return ((a.flags&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) ? -1 : 1;
558            }
559            if (a.priority != b.priority) {
560                return a.priority > b.priority ? -1 : 1;
561            }
562            return sCollator.compare(a.mLabel, b.mLabel);
563        }
564    }
565
566    private static class PermissionInfoComparator implements Comparator<MyPermissionInfo> {
567        private final Collator sCollator = Collator.getInstance();
568        PermissionInfoComparator() {
569        }
570        public final int compare(MyPermissionInfo a, MyPermissionInfo b) {
571            return sCollator.compare(a.mLabel, b.mLabel);
572        }
573    }
574
575    private void addPermToList(List<MyPermissionInfo> permList,
576            MyPermissionInfo pInfo) {
577        if (pInfo.mLabel == null) {
578            pInfo.mLabel = pInfo.loadLabel(mPm);
579        }
580        int idx = Collections.binarySearch(permList, pInfo, mPermComparator);
581        if(localLOGV) Log.i(TAG, "idx="+idx+", list.size="+permList.size());
582        if (idx < 0) {
583            idx = -idx-1;
584            permList.add(idx, pInfo);
585        }
586    }
587
588    private void setPermissions(List<MyPermissionInfo> permList) {
589        if (permList != null) {
590            // First pass to group permissions
591            for (MyPermissionInfo pInfo : permList) {
592                if(localLOGV) Log.i(TAG, "Processing permission:"+pInfo.name);
593                if(!isDisplayablePermission(pInfo, pInfo.mNewReqFlags, pInfo.mExistingReqFlags)) {
594                    if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" is not displayable");
595                    continue;
596                }
597                MyPermissionGroupInfo group = mPermGroups.get(pInfo.group);
598                if (group != null) {
599                    pInfo.mLabel = pInfo.loadLabel(mPm);
600                    addPermToList(group.mAllPermissions, pInfo);
601                    if (pInfo.mNew) {
602                        addPermToList(group.mNewPermissions, pInfo);
603                    }
604                    if ((group.flags&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) {
605                        addPermToList(group.mPersonalPermissions, pInfo);
606                    } else {
607                        addPermToList(group.mDevicePermissions, pInfo);
608                    }
609                }
610            }
611        }
612
613        for (MyPermissionGroupInfo pgrp : mPermGroups.values()) {
614            pgrp.mLabel = pgrp.loadLabel(mPm);
615            mPermGroupsList.add(pgrp);
616        }
617        Collections.sort(mPermGroupsList, mPermGroupComparator);
618    }
619}
620