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