AppSecurityPermissions.java revision e639da7baa23121e35aa06d6e182558e0e755696
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.content.Context;
22import android.content.pm.PackageInfo;
23import android.content.pm.PackageManager;
24import android.content.pm.PackageManager.NameNotFoundException;
25import android.content.pm.PackageParser;
26import android.content.pm.PermissionGroupInfo;
27import android.content.pm.PermissionInfo;
28import android.graphics.drawable.Drawable;
29import android.os.Parcel;
30import android.text.Spannable;
31import android.text.SpannableString;
32import android.text.SpannableStringBuilder;
33import android.text.TextUtils;
34import android.text.style.ForegroundColorSpan;
35import android.util.Log;
36import android.view.LayoutInflater;
37import android.view.View;
38
39import java.text.Collator;
40import java.util.ArrayList;
41import java.util.Collections;
42import java.util.Comparator;
43import java.util.HashMap;
44import java.util.HashSet;
45import java.util.Iterator;
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  implements View.OnClickListener {
62
63    private enum State {
64        NO_PERMS,
65        DANGEROUS_ONLY,
66        NORMAL_ONLY,
67        BOTH
68    }
69
70    static class MyPermissionInfo extends PermissionInfo {
71        /**
72         * PackageInfo.requestedPermissionsFlags for the new package being installed.
73         */
74        int mNewReqFlags;
75
76        /**
77         * PackageInfo.requestedPermissionsFlags for the currently installed
78         * package, if it is installed.
79         */
80        int mExistingReqFlags;
81
82        /**
83         * True if this should be considered a new permission.
84         */
85        boolean mNew;
86
87        MyPermissionInfo() {
88        }
89
90        MyPermissionInfo(PermissionInfo info) {
91            super(info);
92        }
93
94        MyPermissionInfo(MyPermissionInfo info) {
95            super(info);
96            mNewReqFlags = info.mNewReqFlags;
97            mExistingReqFlags = info.mExistingReqFlags;
98            mNew = info.mNew;
99        }
100    }
101
102    private final static String TAG = "AppSecurityPermissions";
103    private boolean localLOGV = false;
104    private Context mContext;
105    private LayoutInflater mInflater;
106    private PackageManager mPm;
107    private LinearLayout mPermsView;
108    private Map<String, CharSequence> mNewMap;
109    private Map<String, CharSequence> mDangerousMap;
110    private Map<String, CharSequence> mNormalMap;
111    private List<MyPermissionInfo> mPermsList;
112    private String mDefaultGrpLabel;
113    private String mDefaultGrpName="DefaultGrp";
114    private String mPermFormat;
115    private CharSequence mNewPermPrefix;
116    private Drawable mNormalIcon;
117    private Drawable mDangerousIcon;
118    private boolean mExpanded;
119    private Drawable mShowMaxIcon;
120    private Drawable mShowMinIcon;
121    private View mShowMore;
122    private TextView mShowMoreText;
123    private ImageView mShowMoreIcon;
124    private State mCurrentState;
125    private LinearLayout mNonDangerousList;
126    private LinearLayout mDangerousList;
127    private LinearLayout mNewList;
128    private HashMap<String, CharSequence> mGroupLabelCache;
129    private View mNoPermsView;
130
131    public AppSecurityPermissions(Context context, List<PermissionInfo> permList) {
132        mContext = context;
133        mPm = mContext.getPackageManager();
134        for (PermissionInfo pi : permList) {
135            mPermsList.add(new MyPermissionInfo(pi));
136        }
137    }
138
139    public AppSecurityPermissions(Context context, String packageName) {
140        mContext = context;
141        mPm = mContext.getPackageManager();
142        mPermsList = new ArrayList<MyPermissionInfo>();
143        Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>();
144        PackageInfo pkgInfo;
145        try {
146            pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
147        } catch (NameNotFoundException e) {
148            Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName);
149            return;
150        }
151        // Extract all user permissions
152        if((pkgInfo.applicationInfo != null) && (pkgInfo.applicationInfo.uid != -1)) {
153            getAllUsedPermissions(pkgInfo.applicationInfo.uid, permSet);
154        }
155        for(MyPermissionInfo tmpInfo : permSet) {
156            mPermsList.add(tmpInfo);
157        }
158    }
159
160    public AppSecurityPermissions(Context context, PackageParser.Package pkg) {
161        mContext = context;
162        mPm = mContext.getPackageManager();
163        mPermsList = new ArrayList<MyPermissionInfo>();
164        Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>();
165        if(pkg == null) {
166            return;
167        }
168
169        // Convert to a PackageInfo
170        PackageInfo info = PackageParser.generatePackageInfo(pkg, null,
171                PackageManager.GET_PERMISSIONS, 0, 0, null);
172        PackageInfo installedPkgInfo = null;
173        // Get requested permissions
174        if (info.requestedPermissions != null) {
175            try {
176                installedPkgInfo = mPm.getPackageInfo(info.packageName,
177                        PackageManager.GET_PERMISSIONS);
178            } catch (NameNotFoundException e) {
179            }
180            extractPerms(info, permSet, installedPkgInfo);
181        }
182        // Get permissions related to  shared user if any
183        if (pkg.mSharedUserId != null) {
184            int sharedUid;
185            try {
186                sharedUid = mPm.getUidForSharedUser(pkg.mSharedUserId);
187                getAllUsedPermissions(sharedUid, permSet);
188            } catch (NameNotFoundException e) {
189                Log.w(TAG, "Could'nt retrieve shared user id for:"+pkg.packageName);
190            }
191        }
192        // Retrieve list of permissions
193        for (MyPermissionInfo tmpInfo : permSet) {
194            mPermsList.add(tmpInfo);
195        }
196    }
197
198    /**
199     * Utility to retrieve a view displaying a single permission.
200     */
201    public static View getPermissionItemView(Context context,
202            CharSequence grpName, CharSequence description, boolean dangerous) {
203        LayoutInflater inflater = (LayoutInflater)context.getSystemService(
204                Context.LAYOUT_INFLATER_SERVICE);
205        Drawable icon = context.getResources().getDrawable(dangerous
206                ? R.drawable.ic_bullet_key_permission : R.drawable.ic_text_dot);
207        return getPermissionItemView(context, inflater, grpName,
208                description, dangerous, icon);
209    }
210
211    private void getAllUsedPermissions(int sharedUid, Set<MyPermissionInfo> permSet) {
212        String sharedPkgList[] = mPm.getPackagesForUid(sharedUid);
213        if(sharedPkgList == null || (sharedPkgList.length == 0)) {
214            return;
215        }
216        for(String sharedPkg : sharedPkgList) {
217            getPermissionsForPackage(sharedPkg, permSet);
218        }
219    }
220
221    private void getPermissionsForPackage(String packageName,
222            Set<MyPermissionInfo> permSet) {
223        PackageInfo pkgInfo;
224        try {
225            pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
226        } catch (NameNotFoundException e) {
227            Log.w(TAG, "Couldn't retrieve permissions for package:"+packageName);
228            return;
229        }
230        if ((pkgInfo != null) && (pkgInfo.requestedPermissions != null)) {
231            extractPerms(pkgInfo, permSet, pkgInfo);
232        }
233    }
234
235    private void extractPerms(PackageInfo info, Set<MyPermissionInfo> permSet,
236            PackageInfo installedPkgInfo) {
237        String[] strList = info.requestedPermissions;
238        int[] flagsList = info.requestedPermissionsFlags;
239        if ((strList == null) || (strList.length == 0)) {
240            return;
241        }
242        for (int i=0; i<strList.length; i++) {
243            String permName = strList[i];
244            // If we are only looking at an existing app, then we only
245            // care about permissions that have actually been granted to it.
246            if (installedPkgInfo != null && info == installedPkgInfo) {
247                if ((flagsList[i]&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) {
248                    continue;
249                }
250            }
251            try {
252                PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0);
253                if (tmpPermInfo == null) {
254                    continue;
255                }
256                int existingIndex = -1;
257                if (installedPkgInfo != null
258                        && installedPkgInfo.requestedPermissions != null) {
259                    for (int j=0; j<installedPkgInfo.requestedPermissions.length; j++) {
260                        if (permName.equals(installedPkgInfo.requestedPermissions[j])) {
261                            existingIndex = j;
262                            break;
263                        }
264                    }
265                }
266                final int existingFlags = existingIndex >= 0 ?
267                        installedPkgInfo.requestedPermissionsFlags[existingIndex] : 0;
268                if (!isDisplayablePermission(tmpPermInfo, flagsList[i], existingFlags)) {
269                    // This is not a permission that is interesting for the user
270                    // to see, so skip it.
271                    continue;
272                }
273                MyPermissionInfo myPerm = new MyPermissionInfo(tmpPermInfo);
274                myPerm.mNewReqFlags = flagsList[i];
275                myPerm.mExistingReqFlags = existingFlags;
276                // This is a new permission if the app is already installed and
277                // doesn't currently hold this permission.
278                myPerm.mNew = installedPkgInfo != null
279                        && (existingFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0;
280                permSet.add(myPerm);
281            } catch (NameNotFoundException e) {
282                Log.i(TAG, "Ignoring unknown permission:"+permName);
283            }
284        }
285    }
286
287    public int getPermissionCount() {
288        return mPermsList.size();
289    }
290
291    public View getPermissionsView() {
292
293        mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
294        mPermsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null);
295        mShowMore = mPermsView.findViewById(R.id.show_more);
296        mShowMoreIcon = (ImageView) mShowMore.findViewById(R.id.show_more_icon);
297        mShowMoreText = (TextView) mShowMore.findViewById(R.id.show_more_text);
298        mNewList = (LinearLayout) mPermsView.findViewById(R.id.new_perms_list);
299        mDangerousList = (LinearLayout) mPermsView.findViewById(R.id.dangerous_perms_list);
300        mNonDangerousList = (LinearLayout) mPermsView.findViewById(R.id.non_dangerous_perms_list);
301        mNoPermsView = mPermsView.findViewById(R.id.no_permissions);
302
303        // Set up the LinearLayout that acts like a list item.
304        mShowMore.setClickable(true);
305        mShowMore.setOnClickListener(this);
306        mShowMore.setFocusable(true);
307
308        // Pick up from framework resources instead.
309        mDefaultGrpLabel = mContext.getString(R.string.default_permission_group);
310        mPermFormat = mContext.getString(R.string.permissions_format);
311        mNewPermPrefix = mContext.getText(R.string.perms_new_perm_prefix);
312        mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot);
313        mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission);
314        mShowMaxIcon = mContext.getResources().getDrawable(R.drawable.expander_close_holo_dark);
315        mShowMinIcon = mContext.getResources().getDrawable(R.drawable.expander_open_holo_dark);
316
317        // Set permissions view
318        setPermissions(mPermsList);
319        return mPermsView;
320    }
321
322    /**
323     * Utility method that concatenates two strings defined by mPermFormat.
324     * a null value is returned if both str1 and str2 are null, if one of the strings
325     * is null the other non null value is returned without formatting
326     * this is to placate initial error checks
327     */
328    private CharSequence formatPermissions(CharSequence groupDesc, CharSequence permDesc,
329            boolean newPerms) {
330        if (permDesc == null) {
331            return groupDesc;
332        }
333        // Sometimes people write permission names with a trailing period;
334        // strip that if it appears.
335        int len = permDesc.length();
336        if (len > 0 && permDesc.charAt(len-1) == '.') {
337            permDesc = (permDesc.toString()).substring(0, len-1);
338        }
339        if (newPerms) {
340            if (true) {
341                // If this is a new permission, format it appropriately.
342                SpannableStringBuilder builder = new SpannableStringBuilder();
343                if (groupDesc != null) {
344                    // The previous permissions go in front, with a newline
345                    // separating them.
346                    builder.append(groupDesc);
347                    builder.append("\n");
348                }
349                Parcel parcel = Parcel.obtain();
350                TextUtils.writeToParcel(mNewPermPrefix, parcel, 0);
351                parcel.setDataPosition(0);
352                CharSequence newStr = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
353                parcel.recycle();
354                builder.append(newStr);
355                builder.append(permDesc);
356                return builder;
357            } else {
358                // If this is a new permission, format it appropriately.
359                SpannableStringBuilder builder = new SpannableStringBuilder(permDesc);
360                builder.insert(0, mNewPermPrefix);
361                if (groupDesc != null) {
362                    // The previous permissions go in front, with a newline
363                    // separating them.
364                    builder.insert(0, "\n");
365                    builder.insert(0, groupDesc);
366                }
367                return builder;
368            }
369        }
370        if (groupDesc == null) {
371            return permDesc;
372        }
373        // groupDesc and permDesc are non null
374        return String.format(mPermFormat, groupDesc, permDesc.toString());
375    }
376
377    private CharSequence getGroupLabel(String grpName) {
378        if (grpName == null) {
379            //return default label
380            return mDefaultGrpLabel;
381        }
382        CharSequence cachedLabel = mGroupLabelCache.get(grpName);
383        if (cachedLabel != null) {
384            return cachedLabel;
385        }
386        PermissionGroupInfo pgi;
387        try {
388            pgi = mPm.getPermissionGroupInfo(grpName, 0);
389        } catch (NameNotFoundException e) {
390            Log.i(TAG, "Invalid group name:" + grpName);
391            return null;
392        }
393        CharSequence label = pgi.loadLabel(mPm).toString();
394        mGroupLabelCache.put(grpName, label);
395        return label;
396    }
397
398    /**
399     * Utility method that displays permissions from a map containing group name and
400     * list of permission descriptions.
401     */
402    private void displayPermissions(Map<String, CharSequence> permInfoMap,
403            LinearLayout permListView, boolean dangerous) {
404        permListView.removeAllViews();
405
406        Set<String> permInfoStrSet = permInfoMap.keySet();
407        for (String loopPermGrpInfoStr : permInfoStrSet) {
408            CharSequence grpLabel = getGroupLabel(loopPermGrpInfoStr);
409            //guaranteed that grpLabel wont be null since permissions without groups
410            //will belong to the default group
411            if(localLOGV) Log.i(TAG, "Adding view group:" + grpLabel + ", desc:"
412                    + permInfoMap.get(loopPermGrpInfoStr));
413            permListView.addView(getPermissionItemView(grpLabel,
414                    permInfoMap.get(loopPermGrpInfoStr), dangerous));
415        }
416    }
417
418    private void displayNoPermissions() {
419        mNoPermsView.setVisibility(View.VISIBLE);
420    }
421
422    private View getPermissionItemView(CharSequence grpName, CharSequence permList,
423            boolean dangerous) {
424        return getPermissionItemView(mContext, mInflater, grpName, permList,
425                dangerous, dangerous ? mDangerousIcon : mNormalIcon);
426    }
427
428    private static View getPermissionItemView(Context context, LayoutInflater inflater,
429            CharSequence grpName, CharSequence permList, boolean dangerous, Drawable icon) {
430        View permView = inflater.inflate(R.layout.app_permission_item, null);
431
432        TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group);
433        TextView permDescView = (TextView) permView.findViewById(R.id.permission_list);
434
435        ImageView imgView = (ImageView)permView.findViewById(R.id.perm_icon);
436        imgView.setImageDrawable(icon);
437        if(grpName != null) {
438            permGrpView.setText(grpName);
439            permDescView.setText(permList);
440        } else {
441            permGrpView.setText(permList);
442            permDescView.setVisibility(View.GONE);
443        }
444        return permView;
445    }
446
447    private void showPermissions() {
448
449        switch(mCurrentState) {
450        case NO_PERMS:
451            displayNoPermissions();
452            break;
453
454        case DANGEROUS_ONLY:
455            displayPermissions(mNewMap, mNewList, true);
456            displayPermissions(mDangerousMap, mDangerousList, true);
457            break;
458
459        case NORMAL_ONLY:
460            displayPermissions(mNewMap, mNewList, true);
461            displayPermissions(mNormalMap, mNonDangerousList, false);
462            break;
463
464        case BOTH:
465            displayPermissions(mNewMap, mNewList, true);
466            displayPermissions(mDangerousMap, mDangerousList, true);
467            if (mExpanded) {
468                displayPermissions(mNormalMap, mNonDangerousList, false);
469                mShowMoreIcon.setImageDrawable(mShowMaxIcon);
470                mShowMoreText.setText(R.string.perms_hide);
471                mNonDangerousList.setVisibility(View.VISIBLE);
472            } else {
473                mShowMoreIcon.setImageDrawable(mShowMinIcon);
474                mShowMoreText.setText(R.string.perms_show_all);
475                mNonDangerousList.setVisibility(View.GONE);
476            }
477            mShowMore.setVisibility(View.VISIBLE);
478            break;
479        }
480    }
481
482    private boolean isDisplayablePermission(PermissionInfo pInfo, int newReqFlags,
483            int existingReqFlags) {
484        final int base = pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
485        // Dangerous and normal permissions are always shown to the user.
486        if (base == PermissionInfo.PROTECTION_DANGEROUS ||
487                base == PermissionInfo.PROTECTION_NORMAL) {
488            return true;
489        }
490        // Development permissions are only shown to the user if they are already
491        // granted to the app -- if we are installing an app and they are not
492        // already granted, they will not be granted as part of the install.
493        // Note we also need the app to have specified this permission is not
494        // required -- this is not technically needed, but it helps various things
495        // if we ensure apps always mark development permissions as option, so that
496        // even not knowing what a permission is we can still know whether it will
497        // be granted to the app when it is installed.
498        if ((existingReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0
499                && (newReqFlags&PackageInfo.REQUESTED_PERMISSION_REQUIRED) == 0
500                && (pInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
501            return true;
502        }
503        return false;
504    }
505
506    /*
507     * Utility method that aggregates all permission descriptions categorized by group
508     * Say group1 has perm11, perm12, perm13, the group description will be
509     * perm11_Desc, perm12_Desc, perm13_Desc
510     */
511    private void aggregateGroupDescs(Map<String, List<MyPermissionInfo> > map,
512            Map<String, CharSequence> retMap, boolean newPerms) {
513        if(map == null) {
514            return;
515        }
516        if(retMap == null) {
517           return;
518        }
519        Set<String> grpNames = map.keySet();
520        Iterator<String> grpNamesIter = grpNames.iterator();
521        while(grpNamesIter.hasNext()) {
522            CharSequence grpDesc = null;
523            String grpNameKey = grpNamesIter.next();
524            List<MyPermissionInfo> grpPermsList = map.get(grpNameKey);
525            if(grpPermsList == null) {
526                continue;
527            }
528            for(PermissionInfo permInfo: grpPermsList) {
529                CharSequence permDesc = permInfo.loadLabel(mPm);
530                grpDesc = formatPermissions(grpDesc, permDesc, newPerms);
531            }
532            // Insert grpDesc into map
533            if(grpDesc != null) {
534                if(localLOGV) Log.i(TAG, "Group:"+grpNameKey+" description:"+grpDesc.toString());
535                retMap.put(grpNameKey, grpDesc);
536            }
537        }
538    }
539
540    private static class PermissionInfoComparator implements Comparator<PermissionInfo> {
541        private PackageManager mPm;
542        private final Collator sCollator = Collator.getInstance();
543        PermissionInfoComparator(PackageManager pm) {
544            mPm = pm;
545        }
546        public final int compare(PermissionInfo a, PermissionInfo b) {
547            CharSequence sa = a.loadLabel(mPm);
548            CharSequence sb = b.loadLabel(mPm);
549            return sCollator.compare(sa, sb);
550        }
551    }
552
553    private void setPermissions(List<MyPermissionInfo> permList) {
554        mGroupLabelCache = new HashMap<String, CharSequence>();
555        //add the default label so that uncategorized permissions can go here
556        mGroupLabelCache.put(mDefaultGrpName, mDefaultGrpLabel);
557
558        // Map containing group names and a list of permissions under that group
559        // that are new from the current install
560        mNewMap = new HashMap<String, CharSequence>();
561        // Map containing group names and a list of permissions under that group
562        // categorized as dangerous
563        mDangerousMap = new HashMap<String, CharSequence>();
564        // Map containing group names and a list of permissions under that group
565        // categorized as normal
566        mNormalMap = new HashMap<String, CharSequence>();
567
568        // Additional structures needed to ensure that permissions are unique under
569        // each group
570        Map<String, List<MyPermissionInfo>> newMap =
571            new HashMap<String,  List<MyPermissionInfo>>();
572        Map<String, List<MyPermissionInfo>> dangerousMap =
573            new HashMap<String,  List<MyPermissionInfo>>();
574        Map<String, List<MyPermissionInfo> > normalMap =
575            new HashMap<String,  List<MyPermissionInfo>>();
576        PermissionInfoComparator permComparator = new PermissionInfoComparator(mPm);
577
578        if (permList != null) {
579            // First pass to group permissions
580            for (MyPermissionInfo pInfo : permList) {
581                if(localLOGV) Log.i(TAG, "Processing permission:"+pInfo.name);
582                if(!isDisplayablePermission(pInfo, pInfo.mNewReqFlags, pInfo.mExistingReqFlags)) {
583                    if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" is not displayable");
584                    continue;
585                }
586                Map<String, List<MyPermissionInfo> > permInfoMap;
587                if (pInfo.mNew) {
588                    permInfoMap = newMap;
589                } else if ((pInfo.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE)
590                            == PermissionInfo.PROTECTION_DANGEROUS) {
591                    permInfoMap = dangerousMap;
592                } else {
593                    permInfoMap = normalMap;
594                }
595                String grpName = (pInfo.group == null) ? mDefaultGrpName : pInfo.group;
596                if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" belongs to group:"+grpName);
597                List<MyPermissionInfo> grpPermsList = permInfoMap.get(grpName);
598                if(grpPermsList == null) {
599                    grpPermsList = new ArrayList<MyPermissionInfo>();
600                    permInfoMap.put(grpName, grpPermsList);
601                    grpPermsList.add(pInfo);
602                } else {
603                    int idx = Collections.binarySearch(grpPermsList, pInfo, permComparator);
604                    if(localLOGV) Log.i(TAG, "idx="+idx+", list.size="+grpPermsList.size());
605                    if (idx < 0) {
606                        idx = -idx-1;
607                        grpPermsList.add(idx, pInfo);
608                    }
609                }
610            }
611            // Second pass to actually form the descriptions
612            // Look at dangerous permissions first
613            aggregateGroupDescs(newMap, mNewMap, true);
614            aggregateGroupDescs(dangerousMap, mDangerousMap, false);
615            aggregateGroupDescs(normalMap, mNormalMap, false);
616        }
617
618        mCurrentState = State.NO_PERMS;
619        if (mNewMap.size() > 0 || mDangerousMap.size() > 0) {
620            mCurrentState = (mNormalMap.size() > 0) ? State.BOTH : State.DANGEROUS_ONLY;
621        } else if(mNormalMap.size() > 0) {
622            mCurrentState = State.NORMAL_ONLY;
623        }
624        if(localLOGV) Log.i(TAG, "mCurrentState=" + mCurrentState);
625        showPermissions();
626    }
627
628    public void onClick(View v) {
629        if(localLOGV) Log.i(TAG, "mExpanded="+mExpanded);
630        mExpanded = !mExpanded;
631        showPermissions();
632    }
633}
634