AppSecurityPermissions.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
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        // Extract shared user permissions if any
128        if(pkg.mSharedUserId != null) {
129            int sharedUid;
130            try {
131                sharedUid = mPm.getUidForSharedUser(pkg.mSharedUserId);
132            } catch (NameNotFoundException e) {
133                Log.w(TAG, "Could'nt retrieve shared user id for:"+pkg.packageName);
134                return;
135            }
136            getAllUsedPermissions(sharedUid, permSet);
137        } else {
138            ArrayList<String> strList = pkg.requestedPermissions;
139            int size;
140            if((strList == null) || ((size = strList.size()) == 0)) {
141                return;
142            }
143            // Extract permissions defined in current package
144            extractPerms(strList.toArray(new String[size]), permSet);
145        }
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) {
180            return;
181        }
182        String strList[] = pkgInfo.requestedPermissions;
183        if(strList == null) {
184            return;
185        }
186        extractPerms(strList, permSet);
187    }
188
189    private void extractPerms(String strList[], Set<PermissionInfo> permSet) {
190        if((strList == null) || (strList.length == 0)) {
191            return;
192        }
193        for(String permName:strList) {
194            try {
195                PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0);
196                if(tmpPermInfo != null) {
197                    permSet.add(tmpPermInfo);
198                }
199            } catch (NameNotFoundException e) {
200                Log.i(TAG, "Ignoring unknown permission:"+permName);
201            }
202        }
203    }
204
205    public int getPermissionCount() {
206        return mPermsList.size();
207    }
208
209    public View getPermissionsView() {
210
211        mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
212        mPermsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null);
213        mShowMore = mPermsView.findViewById(R.id.show_more);
214        mShowMoreIcon = (ImageView) mShowMore.findViewById(R.id.show_more_icon);
215        mShowMoreText = (TextView) mShowMore.findViewById(R.id.show_more_text);
216        mDangerousList = (LinearLayout) mPermsView.findViewById(R.id.dangerous_perms_list);
217        mNonDangerousList = (LinearLayout) mPermsView.findViewById(R.id.non_dangerous_perms_list);
218        mNoPermsView = mPermsView.findViewById(R.id.no_permissions);
219
220        // Set up the LinearLayout that acts like a list item.
221        mShowMore.setClickable(true);
222        mShowMore.setOnClickListener(this);
223        mShowMore.setFocusable(true);
224        mShowMore.setBackgroundResource(android.R.drawable.list_selector_background);
225
226        // Pick up from framework resources instead.
227        mDefaultGrpLabel = mContext.getString(R.string.default_permission_group);
228        mPermFormat = mContext.getString(R.string.permissions_format);
229        mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot);
230        mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission);
231        mShowMaxIcon = mContext.getResources().getDrawable(R.drawable.expander_ic_maximized);
232        mShowMinIcon = mContext.getResources().getDrawable(R.drawable.expander_ic_minimized);
233
234        // Set permissions view
235        setPermissions(mPermsList);
236        return mPermsView;
237    }
238
239    /**
240     * Canonicalizes the group description before it is displayed to the user.
241     *
242     * TODO check for internationalization issues remove trailing '.' in str1
243     */
244    private String canonicalizeGroupDesc(String groupDesc) {
245        if ((groupDesc == null) || (groupDesc.length() == 0)) {
246            return null;
247        }
248        // Both str1 and str2 are non-null and are non-zero in size.
249        int len = groupDesc.length();
250        if(groupDesc.charAt(len-1) == '.') {
251            groupDesc = groupDesc.substring(0, len-1);
252        }
253        return groupDesc;
254    }
255
256    /**
257     * Utility method that concatenates two strings defined by mPermFormat.
258     * a null value is returned if both str1 and str2 are null, if one of the strings
259     * is null the other non null value is returned without formatting
260     * this is to placate initial error checks
261     */
262    private String formatPermissions(String groupDesc, CharSequence permDesc) {
263        if(groupDesc == null) {
264            if(permDesc == null) {
265                return null;
266            }
267            return permDesc.toString();
268        }
269        groupDesc = canonicalizeGroupDesc(groupDesc);
270        if(permDesc == null) {
271            return groupDesc;
272        }
273        // groupDesc and permDesc are non null
274        return String.format(mPermFormat, groupDesc, permDesc.toString());
275    }
276
277    private CharSequence getGroupLabel(String grpName) {
278        if (grpName == null) {
279            //return default label
280            return mDefaultGrpLabel;
281        }
282        CharSequence cachedLabel = mGroupLabelCache.get(grpName);
283        if (cachedLabel != null) {
284            return cachedLabel;
285        }
286        PermissionGroupInfo pgi;
287        try {
288            pgi = mPm.getPermissionGroupInfo(grpName, 0);
289        } catch (NameNotFoundException e) {
290            Log.i(TAG, "Invalid group name:" + grpName);
291            return null;
292        }
293        CharSequence label = pgi.loadLabel(mPm).toString();
294        mGroupLabelCache.put(grpName, label);
295        return label;
296    }
297
298    /**
299     * Utility method that displays permissions from a map containing group name and
300     * list of permission descriptions.
301     */
302    private void displayPermissions(boolean dangerous) {
303        Map<String, String> permInfoMap = dangerous ? mDangerousMap : mNormalMap;
304        LinearLayout permListView = dangerous ? mDangerousList : mNonDangerousList;
305        permListView.removeAllViews();
306
307        Set<String> permInfoStrSet = permInfoMap.keySet();
308        for (String loopPermGrpInfoStr : permInfoStrSet) {
309            CharSequence grpLabel = getGroupLabel(loopPermGrpInfoStr);
310            //guaranteed that grpLabel wont be null since permissions without groups
311            //will belong to the default group
312            if(localLOGV) Log.i(TAG, "Adding view group:" + grpLabel + ", desc:"
313                    + permInfoMap.get(loopPermGrpInfoStr));
314            permListView.addView(getPermissionItemView(grpLabel,
315                    permInfoMap.get(loopPermGrpInfoStr), dangerous));
316        }
317    }
318
319    private void displayNoPermissions() {
320        mNoPermsView.setVisibility(View.VISIBLE);
321    }
322
323    private View getPermissionItemView(CharSequence grpName, String permList,
324            boolean dangerous) {
325        View permView = mInflater.inflate(R.layout.app_permission_item, null);
326        Drawable icon = dangerous ? mDangerousIcon : mNormalIcon;
327        int grpColor = dangerous ? R.color.perms_dangerous_grp_color :
328            R.color.perms_normal_grp_color;
329        int permColor = dangerous ? R.color.perms_dangerous_perm_color :
330            R.color.perms_normal_perm_color;
331
332        TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group);
333        TextView permDescView = (TextView) permView.findViewById(R.id.permission_list);
334        permGrpView.setTextColor(mContext.getResources().getColor(grpColor));
335        permDescView.setTextColor(mContext.getResources().getColor(permColor));
336
337        ImageView imgView = (ImageView)permView.findViewById(R.id.perm_icon);
338        imgView.setImageDrawable(icon);
339        if(grpName != null) {
340            permGrpView.setText(grpName);
341            permDescView.setText(permList);
342        } else {
343            permGrpView.setText(permList);
344            permDescView.setVisibility(View.GONE);
345        }
346        return permView;
347    }
348
349    private void showPermissions() {
350
351        switch(mCurrentState) {
352        case NO_PERMS:
353            displayNoPermissions();
354            break;
355
356        case DANGEROUS_ONLY:
357            displayPermissions(true);
358            break;
359
360        case NORMAL_ONLY:
361            displayPermissions(false);
362            break;
363
364        case BOTH:
365            displayPermissions(true);
366            if (mExpanded) {
367                displayPermissions(false);
368                mShowMoreIcon.setImageDrawable(mShowMaxIcon);
369                mShowMoreText.setText(R.string.perms_hide);
370                mNonDangerousList.setVisibility(View.VISIBLE);
371            } else {
372                mShowMoreIcon.setImageDrawable(mShowMinIcon);
373                mShowMoreText.setText(R.string.perms_show_all);
374                mNonDangerousList.setVisibility(View.GONE);
375            }
376            mShowMore.setVisibility(View.VISIBLE);
377            break;
378        }
379    }
380
381    private boolean isDisplayablePermission(PermissionInfo pInfo) {
382        if(pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS ||
383                pInfo.protectionLevel == PermissionInfo.PROTECTION_NORMAL) {
384            return true;
385        }
386        return false;
387    }
388
389    /*
390     * Utility method that aggregates all permission descriptions categorized by group
391     * Say group1 has perm11, perm12, perm13, the group description will be
392     * perm11_Desc, perm12_Desc, perm13_Desc
393     */
394    private void aggregateGroupDescs(
395            Map<String, List<PermissionInfo> > map, Map<String, String> retMap) {
396        if(map == null) {
397            return;
398        }
399        if(retMap == null) {
400           return;
401        }
402        Set<String> grpNames = map.keySet();
403        Iterator<String> grpNamesIter = grpNames.iterator();
404        while(grpNamesIter.hasNext()) {
405            String grpDesc = null;
406            String grpNameKey = grpNamesIter.next();
407            List<PermissionInfo> grpPermsList = map.get(grpNameKey);
408            if(grpPermsList == null) {
409                continue;
410            }
411            for(PermissionInfo permInfo: grpPermsList) {
412                CharSequence permDesc = permInfo.loadLabel(mPm);
413                grpDesc = formatPermissions(grpDesc, permDesc);
414            }
415            // Insert grpDesc into map
416            if(grpDesc != null) {
417                if(localLOGV) Log.i(TAG, "Group:"+grpNameKey+" description:"+grpDesc.toString());
418                retMap.put(grpNameKey, grpDesc.toString());
419            }
420        }
421    }
422
423    private static class PermissionInfoComparator implements Comparator<PermissionInfo> {
424        private PackageManager mPm;
425        private final Collator sCollator = Collator.getInstance();
426        PermissionInfoComparator(PackageManager pm) {
427            mPm = pm;
428        }
429        public final int compare(PermissionInfo a, PermissionInfo b) {
430            CharSequence sa = a.loadLabel(mPm);
431            CharSequence sb = b.loadLabel(mPm);
432            return sCollator.compare(sa, sb);
433        }
434    }
435
436    private void setPermissions(List<PermissionInfo> permList) {
437        mGroupLabelCache = new HashMap<String, CharSequence>();
438        //add the default label so that uncategorized permissions can go here
439        mGroupLabelCache.put(mDefaultGrpName, mDefaultGrpLabel);
440
441        // Map containing group names and a list of permissions under that group
442        // categorized as dangerous
443        mDangerousMap = new HashMap<String, String>();
444        // Map containing group names and a list of permissions under that group
445        // categorized as normal
446        mNormalMap = new HashMap<String, String>();
447
448        // Additional structures needed to ensure that permissions are unique under
449        // each group
450        Map<String, List<PermissionInfo>> dangerousMap =
451            new HashMap<String,  List<PermissionInfo>>();
452        Map<String, List<PermissionInfo> > normalMap =
453            new HashMap<String,  List<PermissionInfo>>();
454        PermissionInfoComparator permComparator = new PermissionInfoComparator(mPm);
455
456        if (permList != null) {
457            // First pass to group permissions
458            for (PermissionInfo pInfo : permList) {
459                if(localLOGV) Log.i(TAG, "Processing permission:"+pInfo.name);
460                if(!isDisplayablePermission(pInfo)) {
461                    if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" is not displayable");
462                    continue;
463                }
464                Map<String, List<PermissionInfo> > permInfoMap =
465                    (pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) ?
466                            dangerousMap : normalMap;
467                String grpName = (pInfo.group == null) ? mDefaultGrpName : pInfo.group;
468                if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" belongs to group:"+grpName);
469                List<PermissionInfo> grpPermsList = permInfoMap.get(grpName);
470                if(grpPermsList == null) {
471                    grpPermsList = new ArrayList<PermissionInfo>();
472                    permInfoMap.put(grpName, grpPermsList);
473                    grpPermsList.add(pInfo);
474                } else {
475                    int idx = Collections.binarySearch(grpPermsList, pInfo, permComparator);
476                    if(localLOGV) Log.i(TAG, "idx="+idx+", list.size="+grpPermsList.size());
477                    if (idx < 0) {
478                        idx = -idx-1;
479                        grpPermsList.add(idx, pInfo);
480                    }
481                }
482            }
483            // Second pass to actually form the descriptions
484            // Look at dangerous permissions first
485            aggregateGroupDescs(dangerousMap, mDangerousMap);
486            aggregateGroupDescs(normalMap, mNormalMap);
487        }
488
489        mCurrentState = State.NO_PERMS;
490        if(mDangerousMap.size() > 0) {
491            mCurrentState = (mNormalMap.size() > 0) ? State.BOTH : State.DANGEROUS_ONLY;
492        } else if(mNormalMap.size() > 0) {
493            mCurrentState = State.NORMAL_ONLY;
494        }
495        if(localLOGV) Log.i(TAG, "mCurrentState=" + mCurrentState);
496        showPermissions();
497    }
498
499    public void onClick(View v) {
500        if(localLOGV) Log.i(TAG, "mExpanded="+mExpanded);
501        mExpanded = !mExpanded;
502        showPermissions();
503    }
504}
505