Home | History | Annotate | Download | only in widget
      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 */
     17 package android.widget;
     18 
     19 import com.android.internal.R;
     20 
     21 import android.content.Context;
     22 import android.content.pm.PackageInfo;
     23 import android.content.pm.PackageManager;
     24 import android.content.pm.PackageManager.NameNotFoundException;
     25 import android.content.pm.PackageParser;
     26 import android.content.pm.PermissionGroupInfo;
     27 import android.content.pm.PermissionInfo;
     28 import android.graphics.drawable.Drawable;
     29 import android.util.Log;
     30 import android.view.LayoutInflater;
     31 import android.view.View;
     32 
     33 import java.text.Collator;
     34 import java.util.ArrayList;
     35 import java.util.Collections;
     36 import java.util.Comparator;
     37 import java.util.HashMap;
     38 import java.util.HashSet;
     39 import java.util.Iterator;
     40 import java.util.List;
     41 import java.util.Map;
     42 import 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  */
     55 public 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 
    222         // Pick up from framework resources instead.
    223         mDefaultGrpLabel = mContext.getString(R.string.default_permission_group);
    224         mPermFormat = mContext.getString(R.string.permissions_format);
    225         mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot);
    226         mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission);
    227         mShowMaxIcon = mContext.getResources().getDrawable(R.drawable.expander_close_holo_dark);
    228         mShowMinIcon = mContext.getResources().getDrawable(R.drawable.expander_open_holo_dark);
    229 
    230         // Set permissions view
    231         setPermissions(mPermsList);
    232         return mPermsView;
    233     }
    234 
    235     /**
    236      * Canonicalizes the group description before it is displayed to the user.
    237      *
    238      * TODO check for internationalization issues remove trailing '.' in str1
    239      */
    240     private String canonicalizeGroupDesc(String groupDesc) {
    241         if ((groupDesc == null) || (groupDesc.length() == 0)) {
    242             return null;
    243         }
    244         // Both str1 and str2 are non-null and are non-zero in size.
    245         int len = groupDesc.length();
    246         if(groupDesc.charAt(len-1) == '.') {
    247             groupDesc = groupDesc.substring(0, len-1);
    248         }
    249         return groupDesc;
    250     }
    251 
    252     /**
    253      * Utility method that concatenates two strings defined by mPermFormat.
    254      * a null value is returned if both str1 and str2 are null, if one of the strings
    255      * is null the other non null value is returned without formatting
    256      * this is to placate initial error checks
    257      */
    258     private String formatPermissions(String groupDesc, CharSequence permDesc) {
    259         if(groupDesc == null) {
    260             if(permDesc == null) {
    261                 return null;
    262             }
    263             return permDesc.toString();
    264         }
    265         groupDesc = canonicalizeGroupDesc(groupDesc);
    266         if(permDesc == null) {
    267             return groupDesc;
    268         }
    269         // groupDesc and permDesc are non null
    270         return String.format(mPermFormat, groupDesc, permDesc.toString());
    271     }
    272 
    273     private CharSequence getGroupLabel(String grpName) {
    274         if (grpName == null) {
    275             //return default label
    276             return mDefaultGrpLabel;
    277         }
    278         CharSequence cachedLabel = mGroupLabelCache.get(grpName);
    279         if (cachedLabel != null) {
    280             return cachedLabel;
    281         }
    282         PermissionGroupInfo pgi;
    283         try {
    284             pgi = mPm.getPermissionGroupInfo(grpName, 0);
    285         } catch (NameNotFoundException e) {
    286             Log.i(TAG, "Invalid group name:" + grpName);
    287             return null;
    288         }
    289         CharSequence label = pgi.loadLabel(mPm).toString();
    290         mGroupLabelCache.put(grpName, label);
    291         return label;
    292     }
    293 
    294     /**
    295      * Utility method that displays permissions from a map containing group name and
    296      * list of permission descriptions.
    297      */
    298     private void displayPermissions(boolean dangerous) {
    299         Map<String, String> permInfoMap = dangerous ? mDangerousMap : mNormalMap;
    300         LinearLayout permListView = dangerous ? mDangerousList : mNonDangerousList;
    301         permListView.removeAllViews();
    302 
    303         Set<String> permInfoStrSet = permInfoMap.keySet();
    304         for (String loopPermGrpInfoStr : permInfoStrSet) {
    305             CharSequence grpLabel = getGroupLabel(loopPermGrpInfoStr);
    306             //guaranteed that grpLabel wont be null since permissions without groups
    307             //will belong to the default group
    308             if(localLOGV) Log.i(TAG, "Adding view group:" + grpLabel + ", desc:"
    309                     + permInfoMap.get(loopPermGrpInfoStr));
    310             permListView.addView(getPermissionItemView(grpLabel,
    311                     permInfoMap.get(loopPermGrpInfoStr), dangerous));
    312         }
    313     }
    314 
    315     private void displayNoPermissions() {
    316         mNoPermsView.setVisibility(View.VISIBLE);
    317     }
    318 
    319     private View getPermissionItemView(CharSequence grpName, CharSequence permList,
    320             boolean dangerous) {
    321         return getPermissionItemView(mContext, mInflater, grpName, permList,
    322                 dangerous, dangerous ? mDangerousIcon : mNormalIcon);
    323     }
    324 
    325     private static View getPermissionItemView(Context context, LayoutInflater inflater,
    326             CharSequence grpName, CharSequence permList, boolean dangerous, Drawable icon) {
    327         View permView = inflater.inflate(R.layout.app_permission_item, null);
    328 
    329         TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group);
    330         TextView permDescView = (TextView) permView.findViewById(R.id.permission_list);
    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