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