AppSecurityPermissions.java revision c7b14e92075b8a0250ccc8bb4aa21e61d620a708
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.pm.PackageInfo; 22import android.content.pm.PackageManager; 23import android.content.pm.PackageParser; 24import android.content.pm.PermissionGroupInfo; 25import android.content.pm.PermissionInfo; 26import android.content.pm.PackageManager.NameNotFoundException; 27import android.graphics.drawable.Drawable; 28import android.net.Uri; 29import android.util.DisplayMetrics; 30import android.util.Log; 31import android.view.LayoutInflater; 32import android.view.View; 33 34import java.io.File; 35import java.text.Collator; 36import java.util.ArrayList; 37import java.util.Collections; 38import java.util.Comparator; 39import java.util.HashMap; 40import java.util.HashSet; 41import java.util.Iterator; 42import java.util.List; 43import java.util.Map; 44import java.util.Set; 45 46/** 47 * This class contains the SecurityPermissions view implementation. 48 * Initially the package's advanced or dangerous security permissions 49 * are displayed under categorized 50 * groups. Clicking on the additional permissions presents 51 * extended information consisting of all groups and permissions. 52 * To use this view define a LinearLayout or any ViewGroup and add this 53 * view by instantiating AppSecurityPermissions and invoking getPermissionsView. 54 * 55 * {@hide} 56 */ 57public class AppSecurityPermissions implements View.OnClickListener { 58 59 private enum State { 60 NO_PERMS, 61 DANGEROUS_ONLY, 62 NORMAL_ONLY, 63 BOTH 64 } 65 66 private final static String TAG = "AppSecurityPermissions"; 67 private boolean localLOGV = false; 68 private Context mContext; 69 private LayoutInflater mInflater; 70 private PackageManager mPm; 71 private LinearLayout mPermsView; 72 private Map<String, String> mDangerousMap; 73 private Map<String, String> mNormalMap; 74 private List<PermissionInfo> mPermsList; 75 private String mDefaultGrpLabel; 76 private String mDefaultGrpName="DefaultGrp"; 77 private String mPermFormat; 78 private Drawable mNormalIcon; 79 private Drawable mDangerousIcon; 80 private boolean mExpanded; 81 private Drawable mShowMaxIcon; 82 private Drawable mShowMinIcon; 83 private View mShowMore; 84 private TextView mShowMoreText; 85 private ImageView mShowMoreIcon; 86 private State mCurrentState; 87 private LinearLayout mNonDangerousList; 88 private LinearLayout mDangerousList; 89 private HashMap<String, CharSequence> mGroupLabelCache; 90 private View mNoPermsView; 91 92 public AppSecurityPermissions(Context context, List<PermissionInfo> permList) { 93 mContext = context; 94 mPm = mContext.getPackageManager(); 95 mPermsList = permList; 96 } 97 98 public AppSecurityPermissions(Context context, String packageName) { 99 mContext = context; 100 mPm = mContext.getPackageManager(); 101 mPermsList = new ArrayList<PermissionInfo>(); 102 Set<PermissionInfo> permSet = new HashSet<PermissionInfo>(); 103 PackageInfo pkgInfo; 104 try { 105 pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); 106 } catch (NameNotFoundException e) { 107 Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName); 108 return; 109 } 110 // Extract all user permissions 111 if((pkgInfo.applicationInfo != null) && (pkgInfo.applicationInfo.uid != -1)) { 112 getAllUsedPermissions(pkgInfo.applicationInfo.uid, permSet); 113 } 114 for(PermissionInfo tmpInfo : permSet) { 115 mPermsList.add(tmpInfo); 116 } 117 } 118 119 public AppSecurityPermissions(Context context, PackageParser.Package pkg) { 120 mContext = context; 121 mPm = mContext.getPackageManager(); 122 mPermsList = new ArrayList<PermissionInfo>(); 123 Set<PermissionInfo> permSet = new HashSet<PermissionInfo>(); 124 if(pkg == null) { 125 return; 126 } 127 // Get requested permissions 128 if (pkg.requestedPermissions != null) { 129 ArrayList<String> strList = pkg.requestedPermissions; 130 int size = strList.size(); 131 if (size > 0) { 132 extractPerms(strList.toArray(new String[size]), permSet); 133 } 134 } 135 // Get permissions related to shared user if any 136 if(pkg.mSharedUserId != null) { 137 int sharedUid; 138 try { 139 sharedUid = mPm.getUidForSharedUser(pkg.mSharedUserId); 140 getAllUsedPermissions(sharedUid, permSet); 141 } catch (NameNotFoundException e) { 142 Log.w(TAG, "Could'nt retrieve shared user id for:"+pkg.packageName); 143 } 144 } 145 // Retrieve list of permissions 146 for(PermissionInfo tmpInfo : permSet) { 147 mPermsList.add(tmpInfo); 148 } 149 } 150 151 public PackageParser.Package getPackageInfo(Uri packageURI) { 152 final String archiveFilePath = packageURI.getPath(); 153 PackageParser packageParser = new PackageParser(archiveFilePath); 154 File sourceFile = new File(archiveFilePath); 155 DisplayMetrics metrics = new DisplayMetrics(); 156 metrics.setToDefaults(); 157 return packageParser.parsePackage(sourceFile, archiveFilePath, metrics, 0); 158 } 159 160 private void getAllUsedPermissions(int sharedUid, Set<PermissionInfo> permSet) { 161 String sharedPkgList[] = mPm.getPackagesForUid(sharedUid); 162 if(sharedPkgList == null || (sharedPkgList.length == 0)) { 163 return; 164 } 165 for(String sharedPkg : sharedPkgList) { 166 getPermissionsForPackage(sharedPkg, permSet); 167 } 168 } 169 170 private void getPermissionsForPackage(String packageName, 171 Set<PermissionInfo> permSet) { 172 PackageInfo pkgInfo; 173 try { 174 pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); 175 } catch (NameNotFoundException e) { 176 Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName); 177 return; 178 } 179 if ((pkgInfo != null) && (pkgInfo.requestedPermissions != null)) { 180 extractPerms(pkgInfo.requestedPermissions, permSet); 181 } 182 } 183 184 private void extractPerms(String strList[], Set<PermissionInfo> permSet) { 185 if((strList == null) || (strList.length == 0)) { 186 return; 187 } 188 for(String permName:strList) { 189 try { 190 PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0); 191 if(tmpPermInfo != null) { 192 permSet.add(tmpPermInfo); 193 } 194 } catch (NameNotFoundException e) { 195 Log.i(TAG, "Ignoring unknown permission:"+permName); 196 } 197 } 198 } 199 200 public int getPermissionCount() { 201 return mPermsList.size(); 202 } 203 204 public View getPermissionsView() { 205 206 mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 207 mPermsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null); 208 mShowMore = mPermsView.findViewById(R.id.show_more); 209 mShowMoreIcon = (ImageView) mShowMore.findViewById(R.id.show_more_icon); 210 mShowMoreText = (TextView) mShowMore.findViewById(R.id.show_more_text); 211 mDangerousList = (LinearLayout) mPermsView.findViewById(R.id.dangerous_perms_list); 212 mNonDangerousList = (LinearLayout) mPermsView.findViewById(R.id.non_dangerous_perms_list); 213 mNoPermsView = mPermsView.findViewById(R.id.no_permissions); 214 215 // Set up the LinearLayout that acts like a list item. 216 mShowMore.setClickable(true); 217 mShowMore.setOnClickListener(this); 218 mShowMore.setFocusable(true); 219 mShowMore.setBackgroundResource(android.R.drawable.list_selector_background); 220 221 // Pick up from framework resources instead. 222 mDefaultGrpLabel = mContext.getString(R.string.default_permission_group); 223 mPermFormat = mContext.getString(R.string.permissions_format); 224 mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot); 225 mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission); 226 mShowMaxIcon = mContext.getResources().getDrawable(R.drawable.expander_ic_maximized); 227 mShowMinIcon = mContext.getResources().getDrawable(R.drawable.expander_ic_minimized); 228 229 // Set permissions view 230 setPermissions(mPermsList); 231 return mPermsView; 232 } 233 234 /** 235 * Canonicalizes the group description before it is displayed to the user. 236 * 237 * TODO check for internationalization issues remove trailing '.' in str1 238 */ 239 private String canonicalizeGroupDesc(String groupDesc) { 240 if ((groupDesc == null) || (groupDesc.length() == 0)) { 241 return null; 242 } 243 // Both str1 and str2 are non-null and are non-zero in size. 244 int len = groupDesc.length(); 245 if(groupDesc.charAt(len-1) == '.') { 246 groupDesc = groupDesc.substring(0, len-1); 247 } 248 return groupDesc; 249 } 250 251 /** 252 * Utility method that concatenates two strings defined by mPermFormat. 253 * a null value is returned if both str1 and str2 are null, if one of the strings 254 * is null the other non null value is returned without formatting 255 * this is to placate initial error checks 256 */ 257 private String formatPermissions(String groupDesc, CharSequence permDesc) { 258 if(groupDesc == null) { 259 if(permDesc == null) { 260 return null; 261 } 262 return permDesc.toString(); 263 } 264 groupDesc = canonicalizeGroupDesc(groupDesc); 265 if(permDesc == null) { 266 return groupDesc; 267 } 268 // groupDesc and permDesc are non null 269 return String.format(mPermFormat, groupDesc, permDesc.toString()); 270 } 271 272 private CharSequence getGroupLabel(String grpName) { 273 if (grpName == null) { 274 //return default label 275 return mDefaultGrpLabel; 276 } 277 CharSequence cachedLabel = mGroupLabelCache.get(grpName); 278 if (cachedLabel != null) { 279 return cachedLabel; 280 } 281 PermissionGroupInfo pgi; 282 try { 283 pgi = mPm.getPermissionGroupInfo(grpName, 0); 284 } catch (NameNotFoundException e) { 285 Log.i(TAG, "Invalid group name:" + grpName); 286 return null; 287 } 288 CharSequence label = pgi.loadLabel(mPm).toString(); 289 mGroupLabelCache.put(grpName, label); 290 return label; 291 } 292 293 /** 294 * Utility method that displays permissions from a map containing group name and 295 * list of permission descriptions. 296 */ 297 private void displayPermissions(boolean dangerous) { 298 Map<String, String> permInfoMap = dangerous ? mDangerousMap : mNormalMap; 299 LinearLayout permListView = dangerous ? mDangerousList : mNonDangerousList; 300 permListView.removeAllViews(); 301 302 Set<String> permInfoStrSet = permInfoMap.keySet(); 303 for (String loopPermGrpInfoStr : permInfoStrSet) { 304 CharSequence grpLabel = getGroupLabel(loopPermGrpInfoStr); 305 //guaranteed that grpLabel wont be null since permissions without groups 306 //will belong to the default group 307 if(localLOGV) Log.i(TAG, "Adding view group:" + grpLabel + ", desc:" 308 + permInfoMap.get(loopPermGrpInfoStr)); 309 permListView.addView(getPermissionItemView(grpLabel, 310 permInfoMap.get(loopPermGrpInfoStr), dangerous)); 311 } 312 } 313 314 private void displayNoPermissions() { 315 mNoPermsView.setVisibility(View.VISIBLE); 316 } 317 318 private View getPermissionItemView(CharSequence grpName, String permList, 319 boolean dangerous) { 320 View permView = mInflater.inflate(R.layout.app_permission_item, null); 321 Drawable icon = dangerous ? mDangerousIcon : mNormalIcon; 322 int grpColor = dangerous ? R.color.perms_dangerous_grp_color : 323 R.color.perms_normal_grp_color; 324 int permColor = dangerous ? R.color.perms_dangerous_perm_color : 325 R.color.perms_normal_perm_color; 326 327 TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group); 328 TextView permDescView = (TextView) permView.findViewById(R.id.permission_list); 329 permGrpView.setTextColor(mContext.getResources().getColor(grpColor)); 330 permDescView.setTextColor(mContext.getResources().getColor(permColor)); 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