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