/* ** ** Copyright 2007, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ package android.widget; import com.android.internal.R; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageParser; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.graphics.drawable.Drawable; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import java.text.Collator; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * This class contains the SecurityPermissions view implementation. * Initially the package's advanced or dangerous security permissions * are displayed under categorized * groups. Clicking on the additional permissions presents * extended information consisting of all groups and permissions. * To use this view define a LinearLayout or any ViewGroup and add this * view by instantiating AppSecurityPermissions and invoking getPermissionsView. * * {@hide} */ public class AppSecurityPermissions implements View.OnClickListener { private enum State { NO_PERMS, DANGEROUS_ONLY, NORMAL_ONLY, BOTH } private final static String TAG = "AppSecurityPermissions"; private boolean localLOGV = false; private Context mContext; private LayoutInflater mInflater; private PackageManager mPm; private LinearLayout mPermsView; private Map<String, String> mDangerousMap; private Map<String, String> mNormalMap; private List<PermissionInfo> mPermsList; private String mDefaultGrpLabel; private String mDefaultGrpName="DefaultGrp"; private String mPermFormat; private Drawable mNormalIcon; private Drawable mDangerousIcon; private boolean mExpanded; private Drawable mShowMaxIcon; private Drawable mShowMinIcon; private View mShowMore; private TextView mShowMoreText; private ImageView mShowMoreIcon; private State mCurrentState; private LinearLayout mNonDangerousList; private LinearLayout mDangerousList; private HashMap<String, CharSequence> mGroupLabelCache; private View mNoPermsView; public AppSecurityPermissions(Context context, List<PermissionInfo> permList) { mContext = context; mPm = mContext.getPackageManager(); mPermsList = permList; } public AppSecurityPermissions(Context context, String packageName) { mContext = context; mPm = mContext.getPackageManager(); mPermsList = new ArrayList<PermissionInfo>(); Set<PermissionInfo> permSet = new HashSet<PermissionInfo>(); PackageInfo pkgInfo; try { pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); } catch (NameNotFoundException e) { Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName); return; } // Extract all user permissions if((pkgInfo.applicationInfo != null) && (pkgInfo.applicationInfo.uid != -1)) { getAllUsedPermissions(pkgInfo.applicationInfo.uid, permSet); } for(PermissionInfo tmpInfo : permSet) { mPermsList.add(tmpInfo); } } public AppSecurityPermissions(Context context, PackageParser.Package pkg) { mContext = context; mPm = mContext.getPackageManager(); mPermsList = new ArrayList<PermissionInfo>(); Set<PermissionInfo> permSet = new HashSet<PermissionInfo>(); if(pkg == null) { return; } // Get requested permissions if (pkg.requestedPermissions != null) { ArrayList<String> strList = pkg.requestedPermissions; int size = strList.size(); if (size > 0) { extractPerms(strList.toArray(new String[size]), permSet); } } // Get permissions related to shared user if any if(pkg.mSharedUserId != null) { int sharedUid; try { sharedUid = mPm.getUidForSharedUser(pkg.mSharedUserId); getAllUsedPermissions(sharedUid, permSet); } catch (NameNotFoundException e) { Log.w(TAG, "Could'nt retrieve shared user id for:"+pkg.packageName); } } // Retrieve list of permissions for(PermissionInfo tmpInfo : permSet) { mPermsList.add(tmpInfo); } } /** * Utility to retrieve a view displaying a single permission. */ public static View getPermissionItemView(Context context, CharSequence grpName, CharSequence description, boolean dangerous) { LayoutInflater inflater = (LayoutInflater)context.getSystemService( Context.LAYOUT_INFLATER_SERVICE); Drawable icon = context.getResources().getDrawable(dangerous ? R.drawable.ic_bullet_key_permission : R.drawable.ic_text_dot); return getPermissionItemView(context, inflater, grpName, description, dangerous, icon); } private void getAllUsedPermissions(int sharedUid, Set<PermissionInfo> permSet) { String sharedPkgList[] = mPm.getPackagesForUid(sharedUid); if(sharedPkgList == null || (sharedPkgList.length == 0)) { return; } for(String sharedPkg : sharedPkgList) { getPermissionsForPackage(sharedPkg, permSet); } } private void getPermissionsForPackage(String packageName, Set<PermissionInfo> permSet) { PackageInfo pkgInfo; try { pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); } catch (NameNotFoundException e) { Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName); return; } if ((pkgInfo != null) && (pkgInfo.requestedPermissions != null)) { extractPerms(pkgInfo.requestedPermissions, permSet); } } private void extractPerms(String strList[], Set<PermissionInfo> permSet) { if((strList == null) || (strList.length == 0)) { return; } for(String permName:strList) { try { PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0); if(tmpPermInfo != null) { permSet.add(tmpPermInfo); } } catch (NameNotFoundException e) { Log.i(TAG, "Ignoring unknown permission:"+permName); } } } public int getPermissionCount() { return mPermsList.size(); } public View getPermissionsView() { mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mPermsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null); mShowMore = mPermsView.findViewById(R.id.show_more); mShowMoreIcon = (ImageView) mShowMore.findViewById(R.id.show_more_icon); mShowMoreText = (TextView) mShowMore.findViewById(R.id.show_more_text); mDangerousList = (LinearLayout) mPermsView.findViewById(R.id.dangerous_perms_list); mNonDangerousList = (LinearLayout) mPermsView.findViewById(R.id.non_dangerous_perms_list); mNoPermsView = mPermsView.findViewById(R.id.no_permissions); // Set up the LinearLayout that acts like a list item. mShowMore.setClickable(true); mShowMore.setOnClickListener(this); mShowMore.setFocusable(true); // Pick up from framework resources instead. mDefaultGrpLabel = mContext.getString(R.string.default_permission_group); mPermFormat = mContext.getString(R.string.permissions_format); mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot); mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission); mShowMaxIcon = mContext.getResources().getDrawable(R.drawable.expander_close_holo_dark); mShowMinIcon = mContext.getResources().getDrawable(R.drawable.expander_open_holo_dark); // Set permissions view setPermissions(mPermsList); return mPermsView; } /** * Canonicalizes the group description before it is displayed to the user. * * TODO check for internationalization issues remove trailing '.' in str1 */ private String canonicalizeGroupDesc(String groupDesc) { if ((groupDesc == null) || (groupDesc.length() == 0)) { return null; } // Both str1 and str2 are non-null and are non-zero in size. int len = groupDesc.length(); if(groupDesc.charAt(len-1) == '.') { groupDesc = groupDesc.substring(0, len-1); } return groupDesc; } /** * Utility method that concatenates two strings defined by mPermFormat. * a null value is returned if both str1 and str2 are null, if one of the strings * is null the other non null value is returned without formatting * this is to placate initial error checks */ private String formatPermissions(String groupDesc, CharSequence permDesc) { if(groupDesc == null) { if(permDesc == null) { return null; } return permDesc.toString(); } groupDesc = canonicalizeGroupDesc(groupDesc); if(permDesc == null) { return groupDesc; } // groupDesc and permDesc are non null return String.format(mPermFormat, groupDesc, permDesc.toString()); } private CharSequence getGroupLabel(String grpName) { if (grpName == null) { //return default label return mDefaultGrpLabel; } CharSequence cachedLabel = mGroupLabelCache.get(grpName); if (cachedLabel != null) { return cachedLabel; } PermissionGroupInfo pgi; try { pgi = mPm.getPermissionGroupInfo(grpName, 0); } catch (NameNotFoundException e) { Log.i(TAG, "Invalid group name:" + grpName); return null; } CharSequence label = pgi.loadLabel(mPm).toString(); mGroupLabelCache.put(grpName, label); return label; } /** * Utility method that displays permissions from a map containing group name and * list of permission descriptions. */ private void displayPermissions(boolean dangerous) { Map<String, String> permInfoMap = dangerous ? mDangerousMap : mNormalMap; LinearLayout permListView = dangerous ? mDangerousList : mNonDangerousList; permListView.removeAllViews(); Set<String> permInfoStrSet = permInfoMap.keySet(); for (String loopPermGrpInfoStr : permInfoStrSet) { CharSequence grpLabel = getGroupLabel(loopPermGrpInfoStr); //guaranteed that grpLabel wont be null since permissions without groups //will belong to the default group if(localLOGV) Log.i(TAG, "Adding view group:" + grpLabel + ", desc:" + permInfoMap.get(loopPermGrpInfoStr)); permListView.addView(getPermissionItemView(grpLabel, permInfoMap.get(loopPermGrpInfoStr), dangerous)); } } private void displayNoPermissions() { mNoPermsView.setVisibility(View.VISIBLE); } private View getPermissionItemView(CharSequence grpName, CharSequence permList, boolean dangerous) { return getPermissionItemView(mContext, mInflater, grpName, permList, dangerous, dangerous ? mDangerousIcon : mNormalIcon); } private static View getPermissionItemView(Context context, LayoutInflater inflater, CharSequence grpName, CharSequence permList, boolean dangerous, Drawable icon) { View permView = inflater.inflate(R.layout.app_permission_item, null); TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group); TextView permDescView = (TextView) permView.findViewById(R.id.permission_list); ImageView imgView = (ImageView)permView.findViewById(R.id.perm_icon); imgView.setImageDrawable(icon); if(grpName != null) { permGrpView.setText(grpName); permDescView.setText(permList); } else { permGrpView.setText(permList); permDescView.setVisibility(View.GONE); } return permView; } private void showPermissions() { switch(mCurrentState) { case NO_PERMS: displayNoPermissions(); break; case DANGEROUS_ONLY: displayPermissions(true); break; case NORMAL_ONLY: displayPermissions(false); break; case BOTH: displayPermissions(true); if (mExpanded) { displayPermissions(false); mShowMoreIcon.setImageDrawable(mShowMaxIcon); mShowMoreText.setText(R.string.perms_hide); mNonDangerousList.setVisibility(View.VISIBLE); } else { mShowMoreIcon.setImageDrawable(mShowMinIcon); mShowMoreText.setText(R.string.perms_show_all); mNonDangerousList.setVisibility(View.GONE); } mShowMore.setVisibility(View.VISIBLE); break; } } private boolean isDisplayablePermission(PermissionInfo pInfo) { if(pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS || pInfo.protectionLevel == PermissionInfo.PROTECTION_NORMAL) { return true; } return false; } /* * Utility method that aggregates all permission descriptions categorized by group * Say group1 has perm11, perm12, perm13, the group description will be * perm11_Desc, perm12_Desc, perm13_Desc */ private void aggregateGroupDescs( Map<String, List<PermissionInfo> > map, Map<String, String> retMap) { if(map == null) { return; } if(retMap == null) { return; } Set<String> grpNames = map.keySet(); Iterator<String> grpNamesIter = grpNames.iterator(); while(grpNamesIter.hasNext()) { String grpDesc = null; String grpNameKey = grpNamesIter.next(); List<PermissionInfo> grpPermsList = map.get(grpNameKey); if(grpPermsList == null) { continue; } for(PermissionInfo permInfo: grpPermsList) { CharSequence permDesc = permInfo.loadLabel(mPm); grpDesc = formatPermissions(grpDesc, permDesc); } // Insert grpDesc into map if(grpDesc != null) { if(localLOGV) Log.i(TAG, "Group:"+grpNameKey+" description:"+grpDesc.toString()); retMap.put(grpNameKey, grpDesc.toString()); } } } private static class PermissionInfoComparator implements Comparator<PermissionInfo> { private PackageManager mPm; private final Collator sCollator = Collator.getInstance(); PermissionInfoComparator(PackageManager pm) { mPm = pm; } public final int compare(PermissionInfo a, PermissionInfo b) { CharSequence sa = a.loadLabel(mPm); CharSequence sb = b.loadLabel(mPm); return sCollator.compare(sa, sb); } } private void setPermissions(List<PermissionInfo> permList) { mGroupLabelCache = new HashMap<String, CharSequence>(); //add the default label so that uncategorized permissions can go here mGroupLabelCache.put(mDefaultGrpName, mDefaultGrpLabel); // Map containing group names and a list of permissions under that group // categorized as dangerous mDangerousMap = new HashMap<String, String>(); // Map containing group names and a list of permissions under that group // categorized as normal mNormalMap = new HashMap<String, String>(); // Additional structures needed to ensure that permissions are unique under // each group Map<String, List<PermissionInfo>> dangerousMap = new HashMap<String, List<PermissionInfo>>(); Map<String, List<PermissionInfo> > normalMap = new HashMap<String, List<PermissionInfo>>(); PermissionInfoComparator permComparator = new PermissionInfoComparator(mPm); if (permList != null) { // First pass to group permissions for (PermissionInfo pInfo : permList) { if(localLOGV) Log.i(TAG, "Processing permission:"+pInfo.name); if(!isDisplayablePermission(pInfo)) { if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" is not displayable"); continue; } Map<String, List<PermissionInfo> > permInfoMap = (pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) ? dangerousMap : normalMap; String grpName = (pInfo.group == null) ? mDefaultGrpName : pInfo.group; if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" belongs to group:"+grpName); List<PermissionInfo> grpPermsList = permInfoMap.get(grpName); if(grpPermsList == null) { grpPermsList = new ArrayList<PermissionInfo>(); permInfoMap.put(grpName, grpPermsList); grpPermsList.add(pInfo); } else { int idx = Collections.binarySearch(grpPermsList, pInfo, permComparator); if(localLOGV) Log.i(TAG, "idx="+idx+", list.size="+grpPermsList.size()); if (idx < 0) { idx = -idx-1; grpPermsList.add(idx, pInfo); } } } // Second pass to actually form the descriptions // Look at dangerous permissions first aggregateGroupDescs(dangerousMap, mDangerousMap); aggregateGroupDescs(normalMap, mNormalMap); } mCurrentState = State.NO_PERMS; if(mDangerousMap.size() > 0) { mCurrentState = (mNormalMap.size() > 0) ? State.BOTH : State.DANGEROUS_ONLY; } else if(mNormalMap.size() > 0) { mCurrentState = State.NORMAL_ONLY; } if(localLOGV) Log.i(TAG, "mCurrentState=" + mCurrentState); showPermissions(); } public void onClick(View v) { if(localLOGV) Log.i(TAG, "mExpanded="+mExpanded); mExpanded = !mExpanded; showPermissions(); } }