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