AppSecurityPermissions.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
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 // Extract shared user permissions if any 128 if(pkg.mSharedUserId != null) { 129 int sharedUid; 130 try { 131 sharedUid = mPm.getUidForSharedUser(pkg.mSharedUserId); 132 } catch (NameNotFoundException e) { 133 Log.w(TAG, "Could'nt retrieve shared user id for:"+pkg.packageName); 134 return; 135 } 136 getAllUsedPermissions(sharedUid, permSet); 137 } else { 138 ArrayList<String> strList = pkg.requestedPermissions; 139 int size; 140 if((strList == null) || ((size = strList.size()) == 0)) { 141 return; 142 } 143 // Extract permissions defined in current package 144 extractPerms(strList.toArray(new String[size]), permSet); 145 } 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) { 180 return; 181 } 182 String strList[] = pkgInfo.requestedPermissions; 183 if(strList == null) { 184 return; 185 } 186 extractPerms(strList, permSet); 187 } 188 189 private void extractPerms(String strList[], Set<PermissionInfo> permSet) { 190 if((strList == null) || (strList.length == 0)) { 191 return; 192 } 193 for(String permName:strList) { 194 try { 195 PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0); 196 if(tmpPermInfo != null) { 197 permSet.add(tmpPermInfo); 198 } 199 } catch (NameNotFoundException e) { 200 Log.i(TAG, "Ignoring unknown permission:"+permName); 201 } 202 } 203 } 204 205 public int getPermissionCount() { 206 return mPermsList.size(); 207 } 208 209 public View getPermissionsView() { 210 211 mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 212 mPermsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null); 213 mShowMore = mPermsView.findViewById(R.id.show_more); 214 mShowMoreIcon = (ImageView) mShowMore.findViewById(R.id.show_more_icon); 215 mShowMoreText = (TextView) mShowMore.findViewById(R.id.show_more_text); 216 mDangerousList = (LinearLayout) mPermsView.findViewById(R.id.dangerous_perms_list); 217 mNonDangerousList = (LinearLayout) mPermsView.findViewById(R.id.non_dangerous_perms_list); 218 mNoPermsView = mPermsView.findViewById(R.id.no_permissions); 219 220 // Set up the LinearLayout that acts like a list item. 221 mShowMore.setClickable(true); 222 mShowMore.setOnClickListener(this); 223 mShowMore.setFocusable(true); 224 mShowMore.setBackgroundResource(android.R.drawable.list_selector_background); 225 226 // Pick up from framework resources instead. 227 mDefaultGrpLabel = mContext.getString(R.string.default_permission_group); 228 mPermFormat = mContext.getString(R.string.permissions_format); 229 mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot); 230 mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission); 231 mShowMaxIcon = mContext.getResources().getDrawable(R.drawable.expander_ic_maximized); 232 mShowMinIcon = mContext.getResources().getDrawable(R.drawable.expander_ic_minimized); 233 234 // Set permissions view 235 setPermissions(mPermsList); 236 return mPermsView; 237 } 238 239 /** 240 * Canonicalizes the group description before it is displayed to the user. 241 * 242 * TODO check for internationalization issues remove trailing '.' in str1 243 */ 244 private String canonicalizeGroupDesc(String groupDesc) { 245 if ((groupDesc == null) || (groupDesc.length() == 0)) { 246 return null; 247 } 248 // Both str1 and str2 are non-null and are non-zero in size. 249 int len = groupDesc.length(); 250 if(groupDesc.charAt(len-1) == '.') { 251 groupDesc = groupDesc.substring(0, len-1); 252 } 253 return groupDesc; 254 } 255 256 /** 257 * Utility method that concatenates two strings defined by mPermFormat. 258 * a null value is returned if both str1 and str2 are null, if one of the strings 259 * is null the other non null value is returned without formatting 260 * this is to placate initial error checks 261 */ 262 private String formatPermissions(String groupDesc, CharSequence permDesc) { 263 if(groupDesc == null) { 264 if(permDesc == null) { 265 return null; 266 } 267 return permDesc.toString(); 268 } 269 groupDesc = canonicalizeGroupDesc(groupDesc); 270 if(permDesc == null) { 271 return groupDesc; 272 } 273 // groupDesc and permDesc are non null 274 return String.format(mPermFormat, groupDesc, permDesc.toString()); 275 } 276 277 private CharSequence getGroupLabel(String grpName) { 278 if (grpName == null) { 279 //return default label 280 return mDefaultGrpLabel; 281 } 282 CharSequence cachedLabel = mGroupLabelCache.get(grpName); 283 if (cachedLabel != null) { 284 return cachedLabel; 285 } 286 PermissionGroupInfo pgi; 287 try { 288 pgi = mPm.getPermissionGroupInfo(grpName, 0); 289 } catch (NameNotFoundException e) { 290 Log.i(TAG, "Invalid group name:" + grpName); 291 return null; 292 } 293 CharSequence label = pgi.loadLabel(mPm).toString(); 294 mGroupLabelCache.put(grpName, label); 295 return label; 296 } 297 298 /** 299 * Utility method that displays permissions from a map containing group name and 300 * list of permission descriptions. 301 */ 302 private void displayPermissions(boolean dangerous) { 303 Map<String, String> permInfoMap = dangerous ? mDangerousMap : mNormalMap; 304 LinearLayout permListView = dangerous ? mDangerousList : mNonDangerousList; 305 permListView.removeAllViews(); 306 307 Set<String> permInfoStrSet = permInfoMap.keySet(); 308 for (String loopPermGrpInfoStr : permInfoStrSet) { 309 CharSequence grpLabel = getGroupLabel(loopPermGrpInfoStr); 310 //guaranteed that grpLabel wont be null since permissions without groups 311 //will belong to the default group 312 if(localLOGV) Log.i(TAG, "Adding view group:" + grpLabel + ", desc:" 313 + permInfoMap.get(loopPermGrpInfoStr)); 314 permListView.addView(getPermissionItemView(grpLabel, 315 permInfoMap.get(loopPermGrpInfoStr), dangerous)); 316 } 317 } 318 319 private void displayNoPermissions() { 320 mNoPermsView.setVisibility(View.VISIBLE); 321 } 322 323 private View getPermissionItemView(CharSequence grpName, String permList, 324 boolean dangerous) { 325 View permView = mInflater.inflate(R.layout.app_permission_item, null); 326 Drawable icon = dangerous ? mDangerousIcon : mNormalIcon; 327 int grpColor = dangerous ? R.color.perms_dangerous_grp_color : 328 R.color.perms_normal_grp_color; 329 int permColor = dangerous ? R.color.perms_dangerous_perm_color : 330 R.color.perms_normal_perm_color; 331 332 TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group); 333 TextView permDescView = (TextView) permView.findViewById(R.id.permission_list); 334 permGrpView.setTextColor(mContext.getResources().getColor(grpColor)); 335 permDescView.setTextColor(mContext.getResources().getColor(permColor)); 336 337 ImageView imgView = (ImageView)permView.findViewById(R.id.perm_icon); 338 imgView.setImageDrawable(icon); 339 if(grpName != null) { 340 permGrpView.setText(grpName); 341 permDescView.setText(permList); 342 } else { 343 permGrpView.setText(permList); 344 permDescView.setVisibility(View.GONE); 345 } 346 return permView; 347 } 348 349 private void showPermissions() { 350 351 switch(mCurrentState) { 352 case NO_PERMS: 353 displayNoPermissions(); 354 break; 355 356 case DANGEROUS_ONLY: 357 displayPermissions(true); 358 break; 359 360 case NORMAL_ONLY: 361 displayPermissions(false); 362 break; 363 364 case BOTH: 365 displayPermissions(true); 366 if (mExpanded) { 367 displayPermissions(false); 368 mShowMoreIcon.setImageDrawable(mShowMaxIcon); 369 mShowMoreText.setText(R.string.perms_hide); 370 mNonDangerousList.setVisibility(View.VISIBLE); 371 } else { 372 mShowMoreIcon.setImageDrawable(mShowMinIcon); 373 mShowMoreText.setText(R.string.perms_show_all); 374 mNonDangerousList.setVisibility(View.GONE); 375 } 376 mShowMore.setVisibility(View.VISIBLE); 377 break; 378 } 379 } 380 381 private boolean isDisplayablePermission(PermissionInfo pInfo) { 382 if(pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS || 383 pInfo.protectionLevel == PermissionInfo.PROTECTION_NORMAL) { 384 return true; 385 } 386 return false; 387 } 388 389 /* 390 * Utility method that aggregates all permission descriptions categorized by group 391 * Say group1 has perm11, perm12, perm13, the group description will be 392 * perm11_Desc, perm12_Desc, perm13_Desc 393 */ 394 private void aggregateGroupDescs( 395 Map<String, List<PermissionInfo> > map, Map<String, String> retMap) { 396 if(map == null) { 397 return; 398 } 399 if(retMap == null) { 400 return; 401 } 402 Set<String> grpNames = map.keySet(); 403 Iterator<String> grpNamesIter = grpNames.iterator(); 404 while(grpNamesIter.hasNext()) { 405 String grpDesc = null; 406 String grpNameKey = grpNamesIter.next(); 407 List<PermissionInfo> grpPermsList = map.get(grpNameKey); 408 if(grpPermsList == null) { 409 continue; 410 } 411 for(PermissionInfo permInfo: grpPermsList) { 412 CharSequence permDesc = permInfo.loadLabel(mPm); 413 grpDesc = formatPermissions(grpDesc, permDesc); 414 } 415 // Insert grpDesc into map 416 if(grpDesc != null) { 417 if(localLOGV) Log.i(TAG, "Group:"+grpNameKey+" description:"+grpDesc.toString()); 418 retMap.put(grpNameKey, grpDesc.toString()); 419 } 420 } 421 } 422 423 private static class PermissionInfoComparator implements Comparator<PermissionInfo> { 424 private PackageManager mPm; 425 private final Collator sCollator = Collator.getInstance(); 426 PermissionInfoComparator(PackageManager pm) { 427 mPm = pm; 428 } 429 public final int compare(PermissionInfo a, PermissionInfo b) { 430 CharSequence sa = a.loadLabel(mPm); 431 CharSequence sb = b.loadLabel(mPm); 432 return sCollator.compare(sa, sb); 433 } 434 } 435 436 private void setPermissions(List<PermissionInfo> permList) { 437 mGroupLabelCache = new HashMap<String, CharSequence>(); 438 //add the default label so that uncategorized permissions can go here 439 mGroupLabelCache.put(mDefaultGrpName, mDefaultGrpLabel); 440 441 // Map containing group names and a list of permissions under that group 442 // categorized as dangerous 443 mDangerousMap = new HashMap<String, String>(); 444 // Map containing group names and a list of permissions under that group 445 // categorized as normal 446 mNormalMap = new HashMap<String, String>(); 447 448 // Additional structures needed to ensure that permissions are unique under 449 // each group 450 Map<String, List<PermissionInfo>> dangerousMap = 451 new HashMap<String, List<PermissionInfo>>(); 452 Map<String, List<PermissionInfo> > normalMap = 453 new HashMap<String, List<PermissionInfo>>(); 454 PermissionInfoComparator permComparator = new PermissionInfoComparator(mPm); 455 456 if (permList != null) { 457 // First pass to group permissions 458 for (PermissionInfo pInfo : permList) { 459 if(localLOGV) Log.i(TAG, "Processing permission:"+pInfo.name); 460 if(!isDisplayablePermission(pInfo)) { 461 if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" is not displayable"); 462 continue; 463 } 464 Map<String, List<PermissionInfo> > permInfoMap = 465 (pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) ? 466 dangerousMap : normalMap; 467 String grpName = (pInfo.group == null) ? mDefaultGrpName : pInfo.group; 468 if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" belongs to group:"+grpName); 469 List<PermissionInfo> grpPermsList = permInfoMap.get(grpName); 470 if(grpPermsList == null) { 471 grpPermsList = new ArrayList<PermissionInfo>(); 472 permInfoMap.put(grpName, grpPermsList); 473 grpPermsList.add(pInfo); 474 } else { 475 int idx = Collections.binarySearch(grpPermsList, pInfo, permComparator); 476 if(localLOGV) Log.i(TAG, "idx="+idx+", list.size="+grpPermsList.size()); 477 if (idx < 0) { 478 idx = -idx-1; 479 grpPermsList.add(idx, pInfo); 480 } 481 } 482 } 483 // Second pass to actually form the descriptions 484 // Look at dangerous permissions first 485 aggregateGroupDescs(dangerousMap, mDangerousMap); 486 aggregateGroupDescs(normalMap, mNormalMap); 487 } 488 489 mCurrentState = State.NO_PERMS; 490 if(mDangerousMap.size() > 0) { 491 mCurrentState = (mNormalMap.size() > 0) ? State.BOTH : State.DANGEROUS_ONLY; 492 } else if(mNormalMap.size() > 0) { 493 mCurrentState = State.NORMAL_ONLY; 494 } 495 if(localLOGV) Log.i(TAG, "mCurrentState=" + mCurrentState); 496 showPermissions(); 497 } 498 499 public void onClick(View v) { 500 if(localLOGV) Log.i(TAG, "mExpanded="+mExpanded); 501 mExpanded = !mExpanded; 502 showPermissions(); 503 } 504} 505