AppSecurityPermissions.java revision c7b14e92075b8a0250ccc8bb4aa21e61d620a708
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;
20import android.content.Context;
21import android.content.pm.PackageInfo;
22import android.content.pm.PackageManager;
23import android.content.pm.PackageParser;
24import android.content.pm.PermissionGroupInfo;
25import android.content.pm.PermissionInfo;
26import android.content.pm.PackageManager.NameNotFoundException;
27import android.graphics.drawable.Drawable;
28import android.net.Uri;
29import android.util.DisplayMetrics;
30import android.util.Log;
31import android.view.LayoutInflater;
32import android.view.View;
33
34import java.io.File;
35import java.text.Collator;
36import java.util.ArrayList;
37import java.util.Collections;
38import java.util.Comparator;
39import java.util.HashMap;
40import java.util.HashSet;
41import java.util.Iterator;
42import java.util.List;
43import java.util.Map;
44import java.util.Set;
45
46/**
47 * This class contains the SecurityPermissions view implementation.
48 * Initially the package's advanced or dangerous security permissions
49 * are displayed under categorized
50 * groups. Clicking on the additional permissions presents
51 * extended information consisting of all groups and permissions.
52 * To use this view define a LinearLayout or any ViewGroup and add this
53 * view by instantiating AppSecurityPermissions and invoking getPermissionsView.
54 *
55 * {@hide}
56 */
57public class AppSecurityPermissions  implements View.OnClickListener {
58
59    private enum State {
60        NO_PERMS,
61        DANGEROUS_ONLY,
62        NORMAL_ONLY,
63        BOTH
64    }
65
66    private final static String TAG = "AppSecurityPermissions";
67    private boolean localLOGV = false;
68    private Context mContext;
69    private LayoutInflater mInflater;
70    private PackageManager mPm;
71    private LinearLayout mPermsView;
72    private Map<String, String> mDangerousMap;
73    private Map<String, String> mNormalMap;
74    private List<PermissionInfo> mPermsList;
75    private String mDefaultGrpLabel;
76    private String mDefaultGrpName="DefaultGrp";
77    private String mPermFormat;
78    private Drawable mNormalIcon;
79    private Drawable mDangerousIcon;
80    private boolean mExpanded;
81    private Drawable mShowMaxIcon;
82    private Drawable mShowMinIcon;
83    private View mShowMore;
84    private TextView mShowMoreText;
85    private ImageView mShowMoreIcon;
86    private State mCurrentState;
87    private LinearLayout mNonDangerousList;
88    private LinearLayout mDangerousList;
89    private HashMap<String, CharSequence> mGroupLabelCache;
90    private View mNoPermsView;
91
92    public AppSecurityPermissions(Context context, List<PermissionInfo> permList) {
93        mContext = context;
94        mPm = mContext.getPackageManager();
95        mPermsList = permList;
96    }
97
98    public AppSecurityPermissions(Context context, String packageName) {
99        mContext = context;
100        mPm = mContext.getPackageManager();
101        mPermsList = new ArrayList<PermissionInfo>();
102        Set<PermissionInfo> permSet = new HashSet<PermissionInfo>();
103        PackageInfo pkgInfo;
104        try {
105            pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
106        } catch (NameNotFoundException e) {
107            Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName);
108            return;
109        }
110        // Extract all user permissions
111        if((pkgInfo.applicationInfo != null) && (pkgInfo.applicationInfo.uid != -1)) {
112            getAllUsedPermissions(pkgInfo.applicationInfo.uid, permSet);
113        }
114        for(PermissionInfo tmpInfo : permSet) {
115            mPermsList.add(tmpInfo);
116        }
117    }
118
119    public AppSecurityPermissions(Context context, PackageParser.Package pkg) {
120        mContext = context;
121        mPm = mContext.getPackageManager();
122        mPermsList = new ArrayList<PermissionInfo>();
123        Set<PermissionInfo> permSet = new HashSet<PermissionInfo>();
124        if(pkg == null) {
125            return;
126        }
127        // Get requested permissions
128        if (pkg.requestedPermissions != null) {
129            ArrayList<String> strList = pkg.requestedPermissions;
130            int size = strList.size();
131            if (size > 0) {
132                extractPerms(strList.toArray(new String[size]), permSet);
133            }
134        }
135        // Get permissions related to  shared user if any
136        if(pkg.mSharedUserId != null) {
137            int sharedUid;
138            try {
139                sharedUid = mPm.getUidForSharedUser(pkg.mSharedUserId);
140                getAllUsedPermissions(sharedUid, permSet);
141            } catch (NameNotFoundException e) {
142                Log.w(TAG, "Could'nt retrieve shared user id for:"+pkg.packageName);
143            }
144        }
145        // Retrieve list of permissions
146        for(PermissionInfo tmpInfo : permSet) {
147            mPermsList.add(tmpInfo);
148        }
149    }
150
151    public PackageParser.Package getPackageInfo(Uri packageURI) {
152        final String archiveFilePath = packageURI.getPath();
153        PackageParser packageParser = new PackageParser(archiveFilePath);
154        File sourceFile = new File(archiveFilePath);
155        DisplayMetrics metrics = new DisplayMetrics();
156        metrics.setToDefaults();
157        return packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0);
158    }
159
160    private void getAllUsedPermissions(int sharedUid, Set<PermissionInfo> permSet) {
161        String sharedPkgList[] = mPm.getPackagesForUid(sharedUid);
162        if(sharedPkgList == null || (sharedPkgList.length == 0)) {
163            return;
164        }
165        for(String sharedPkg : sharedPkgList) {
166            getPermissionsForPackage(sharedPkg, permSet);
167        }
168    }
169
170    private void getPermissionsForPackage(String packageName,
171            Set<PermissionInfo> permSet) {
172        PackageInfo pkgInfo;
173        try {
174            pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
175        } catch (NameNotFoundException e) {
176            Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName);
177            return;
178        }
179        if ((pkgInfo != null) && (pkgInfo.requestedPermissions != null)) {
180            extractPerms(pkgInfo.requestedPermissions, permSet);
181        }
182    }
183
184    private void extractPerms(String strList[], Set<PermissionInfo> permSet) {
185        if((strList == null) || (strList.length == 0)) {
186            return;
187        }
188        for(String permName:strList) {
189            try {
190                PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0);
191                if(tmpPermInfo != null) {
192                    permSet.add(tmpPermInfo);
193                }
194            } catch (NameNotFoundException e) {
195                Log.i(TAG, "Ignoring unknown permission:"+permName);
196            }
197        }
198    }
199
200    public int getPermissionCount() {
201        return mPermsList.size();
202    }
203
204    public View getPermissionsView() {
205
206        mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
207        mPermsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null);
208        mShowMore = mPermsView.findViewById(R.id.show_more);
209        mShowMoreIcon = (ImageView) mShowMore.findViewById(R.id.show_more_icon);
210        mShowMoreText = (TextView) mShowMore.findViewById(R.id.show_more_text);
211        mDangerousList = (LinearLayout) mPermsView.findViewById(R.id.dangerous_perms_list);
212        mNonDangerousList = (LinearLayout) mPermsView.findViewById(R.id.non_dangerous_perms_list);
213        mNoPermsView = mPermsView.findViewById(R.id.no_permissions);
214
215        // Set up the LinearLayout that acts like a list item.
216        mShowMore.setClickable(true);
217        mShowMore.setOnClickListener(this);
218        mShowMore.setFocusable(true);
219        mShowMore.setBackgroundResource(android.R.drawable.list_selector_background);
220
221        // Pick up from framework resources instead.
222        mDefaultGrpLabel = mContext.getString(R.string.default_permission_group);
223        mPermFormat = mContext.getString(R.string.permissions_format);
224        mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot);
225        mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission);
226        mShowMaxIcon = mContext.getResources().getDrawable(R.drawable.expander_ic_maximized);
227        mShowMinIcon = mContext.getResources().getDrawable(R.drawable.expander_ic_minimized);
228
229        // Set permissions view
230        setPermissions(mPermsList);
231        return mPermsView;
232    }
233
234    /**
235     * Canonicalizes the group description before it is displayed to the user.
236     *
237     * TODO check for internationalization issues remove trailing '.' in str1
238     */
239    private String canonicalizeGroupDesc(String groupDesc) {
240        if ((groupDesc == null) || (groupDesc.length() == 0)) {
241            return null;
242        }
243        // Both str1 and str2 are non-null and are non-zero in size.
244        int len = groupDesc.length();
245        if(groupDesc.charAt(len-1) == '.') {
246            groupDesc = groupDesc.substring(0, len-1);
247        }
248        return groupDesc;
249    }
250
251    /**
252     * Utility method that concatenates two strings defined by mPermFormat.
253     * a null value is returned if both str1 and str2 are null, if one of the strings
254     * is null the other non null value is returned without formatting
255     * this is to placate initial error checks
256     */
257    private String formatPermissions(String groupDesc, CharSequence permDesc) {
258        if(groupDesc == null) {
259            if(permDesc == null) {
260                return null;
261            }
262            return permDesc.toString();
263        }
264        groupDesc = canonicalizeGroupDesc(groupDesc);
265        if(permDesc == null) {
266            return groupDesc;
267        }
268        // groupDesc and permDesc are non null
269        return String.format(mPermFormat, groupDesc, permDesc.toString());
270    }
271
272    private CharSequence getGroupLabel(String grpName) {
273        if (grpName == null) {
274            //return default label
275            return mDefaultGrpLabel;
276        }
277        CharSequence cachedLabel = mGroupLabelCache.get(grpName);
278        if (cachedLabel != null) {
279            return cachedLabel;
280        }
281        PermissionGroupInfo pgi;
282        try {
283            pgi = mPm.getPermissionGroupInfo(grpName, 0);
284        } catch (NameNotFoundException e) {
285            Log.i(TAG, "Invalid group name:" + grpName);
286            return null;
287        }
288        CharSequence label = pgi.loadLabel(mPm).toString();
289        mGroupLabelCache.put(grpName, label);
290        return label;
291    }
292
293    /**
294     * Utility method that displays permissions from a map containing group name and
295     * list of permission descriptions.
296     */
297    private void displayPermissions(boolean dangerous) {
298        Map<String, String> permInfoMap = dangerous ? mDangerousMap : mNormalMap;
299        LinearLayout permListView = dangerous ? mDangerousList : mNonDangerousList;
300        permListView.removeAllViews();
301
302        Set<String> permInfoStrSet = permInfoMap.keySet();
303        for (String loopPermGrpInfoStr : permInfoStrSet) {
304            CharSequence grpLabel = getGroupLabel(loopPermGrpInfoStr);
305            //guaranteed that grpLabel wont be null since permissions without groups
306            //will belong to the default group
307            if(localLOGV) Log.i(TAG, "Adding view group:" + grpLabel + ", desc:"
308                    + permInfoMap.get(loopPermGrpInfoStr));
309            permListView.addView(getPermissionItemView(grpLabel,
310                    permInfoMap.get(loopPermGrpInfoStr), dangerous));
311        }
312    }
313
314    private void displayNoPermissions() {
315        mNoPermsView.setVisibility(View.VISIBLE);
316    }
317
318    private View getPermissionItemView(CharSequence grpName, String permList,
319            boolean dangerous) {
320        View permView = mInflater.inflate(R.layout.app_permission_item, null);
321        Drawable icon = dangerous ? mDangerousIcon : mNormalIcon;
322        int grpColor = dangerous ? R.color.perms_dangerous_grp_color :
323            R.color.perms_normal_grp_color;
324        int permColor = dangerous ? R.color.perms_dangerous_perm_color :
325            R.color.perms_normal_perm_color;
326
327        TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group);
328        TextView permDescView = (TextView) permView.findViewById(R.id.permission_list);
329        permGrpView.setTextColor(mContext.getResources().getColor(grpColor));
330        permDescView.setTextColor(mContext.getResources().getColor(permColor));
331
332        ImageView imgView = (ImageView)permView.findViewById(R.id.perm_icon);
333        imgView.setImageDrawable(icon);
334        if(grpName != null) {
335            permGrpView.setText(grpName);
336            permDescView.setText(permList);
337        } else {
338            permGrpView.setText(permList);
339            permDescView.setVisibility(View.GONE);
340        }
341        return permView;
342    }
343
344    private void showPermissions() {
345
346        switch(mCurrentState) {
347        case NO_PERMS:
348            displayNoPermissions();
349            break;
350
351        case DANGEROUS_ONLY:
352            displayPermissions(true);
353            break;
354
355        case NORMAL_ONLY:
356            displayPermissions(false);
357            break;
358
359        case BOTH:
360            displayPermissions(true);
361            if (mExpanded) {
362                displayPermissions(false);
363                mShowMoreIcon.setImageDrawable(mShowMaxIcon);
364                mShowMoreText.setText(R.string.perms_hide);
365                mNonDangerousList.setVisibility(View.VISIBLE);
366            } else {
367                mShowMoreIcon.setImageDrawable(mShowMinIcon);
368                mShowMoreText.setText(R.string.perms_show_all);
369                mNonDangerousList.setVisibility(View.GONE);
370            }
371            mShowMore.setVisibility(View.VISIBLE);
372            break;
373        }
374    }
375
376    private boolean isDisplayablePermission(PermissionInfo pInfo) {
377        if(pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS ||
378                pInfo.protectionLevel == PermissionInfo.PROTECTION_NORMAL) {
379            return true;
380        }
381        return false;
382    }
383
384    /*
385     * Utility method that aggregates all permission descriptions categorized by group
386     * Say group1 has perm11, perm12, perm13, the group description will be
387     * perm11_Desc, perm12_Desc, perm13_Desc
388     */
389    private void aggregateGroupDescs(
390            Map<String, List<PermissionInfo> > map, Map<String, String> retMap) {
391        if(map == null) {
392            return;
393        }
394        if(retMap == null) {
395           return;
396        }
397        Set<String> grpNames = map.keySet();
398        Iterator<String> grpNamesIter = grpNames.iterator();
399        while(grpNamesIter.hasNext()) {
400            String grpDesc = null;
401            String grpNameKey = grpNamesIter.next();
402            List<PermissionInfo> grpPermsList = map.get(grpNameKey);
403            if(grpPermsList == null) {
404                continue;
405            }
406            for(PermissionInfo permInfo: grpPermsList) {
407                CharSequence permDesc = permInfo.loadLabel(mPm);
408                grpDesc = formatPermissions(grpDesc, permDesc);
409            }
410            // Insert grpDesc into map
411            if(grpDesc != null) {
412                if(localLOGV) Log.i(TAG, "Group:"+grpNameKey+" description:"+grpDesc.toString());
413                retMap.put(grpNameKey, grpDesc.toString());
414            }
415        }
416    }
417
418    private static class PermissionInfoComparator implements Comparator<PermissionInfo> {
419        private PackageManager mPm;
420        private final Collator sCollator = Collator.getInstance();
421        PermissionInfoComparator(PackageManager pm) {
422            mPm = pm;
423        }
424        public final int compare(PermissionInfo a, PermissionInfo b) {
425            CharSequence sa = a.loadLabel(mPm);
426            CharSequence sb = b.loadLabel(mPm);
427            return sCollator.compare(sa, sb);
428        }
429    }
430
431    private void setPermissions(List<PermissionInfo> permList) {
432        mGroupLabelCache = new HashMap<String, CharSequence>();
433        //add the default label so that uncategorized permissions can go here
434        mGroupLabelCache.put(mDefaultGrpName, mDefaultGrpLabel);
435
436        // Map containing group names and a list of permissions under that group
437        // categorized as dangerous
438        mDangerousMap = new HashMap<String, String>();
439        // Map containing group names and a list of permissions under that group
440        // categorized as normal
441        mNormalMap = new HashMap<String, String>();
442
443        // Additional structures needed to ensure that permissions are unique under
444        // each group
445        Map<String, List<PermissionInfo>> dangerousMap =
446            new HashMap<String,  List<PermissionInfo>>();
447        Map<String, List<PermissionInfo> > normalMap =
448            new HashMap<String,  List<PermissionInfo>>();
449        PermissionInfoComparator permComparator = new PermissionInfoComparator(mPm);
450
451        if (permList != null) {
452            // First pass to group permissions
453            for (PermissionInfo pInfo : permList) {
454                if(localLOGV) Log.i(TAG, "Processing permission:"+pInfo.name);
455                if(!isDisplayablePermission(pInfo)) {
456                    if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" is not displayable");
457                    continue;
458                }
459                Map<String, List<PermissionInfo> > permInfoMap =
460                    (pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) ?
461                            dangerousMap : normalMap;
462                String grpName = (pInfo.group == null) ? mDefaultGrpName : pInfo.group;
463                if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" belongs to group:"+grpName);
464                List<PermissionInfo> grpPermsList = permInfoMap.get(grpName);
465                if(grpPermsList == null) {
466                    grpPermsList = new ArrayList<PermissionInfo>();
467                    permInfoMap.put(grpName, grpPermsList);
468                    grpPermsList.add(pInfo);
469                } else {
470                    int idx = Collections.binarySearch(grpPermsList, pInfo, permComparator);
471                    if(localLOGV) Log.i(TAG, "idx="+idx+", list.size="+grpPermsList.size());
472                    if (idx < 0) {
473                        idx = -idx-1;
474                        grpPermsList.add(idx, pInfo);
475                    }
476                }
477            }
478            // Second pass to actually form the descriptions
479            // Look at dangerous permissions first
480            aggregateGroupDescs(dangerousMap, mDangerousMap);
481            aggregateGroupDescs(normalMap, mNormalMap);
482        }
483
484        mCurrentState = State.NO_PERMS;
485        if(mDangerousMap.size() > 0) {
486            mCurrentState = (mNormalMap.size() > 0) ? State.BOTH : State.DANGEROUS_ONLY;
487        } else if(mNormalMap.size() > 0) {
488            mCurrentState = State.NORMAL_ONLY;
489        }
490        if(localLOGV) Log.i(TAG, "mCurrentState=" + mCurrentState);
491        showPermissions();
492    }
493
494    public void onClick(View v) {
495        if(localLOGV) Log.i(TAG, "mExpanded="+mExpanded);
496        mExpanded = !mExpanded;
497        showPermissions();
498    }
499}
500