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 */ 17 package android.widget; 18 19 import com.android.internal.R; 20 21 import android.content.Context; 22 import android.content.pm.PackageInfo; 23 import android.content.pm.PackageManager; 24 import android.content.pm.PackageManager.NameNotFoundException; 25 import android.content.pm.PackageParser; 26 import android.content.pm.PermissionGroupInfo; 27 import android.content.pm.PermissionInfo; 28 import android.graphics.drawable.Drawable; 29 import android.util.Log; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 33 import java.text.Collator; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.Comparator; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.Iterator; 40 import java.util.List; 41 import java.util.Map; 42 import 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 */ 55 public 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 222 // Pick up from framework resources instead. 223 mDefaultGrpLabel = mContext.getString(R.string.default_permission_group); 224 mPermFormat = mContext.getString(R.string.permissions_format); 225 mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot); 226 mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission); 227 mShowMaxIcon = mContext.getResources().getDrawable(R.drawable.expander_close_holo_dark); 228 mShowMinIcon = mContext.getResources().getDrawable(R.drawable.expander_open_holo_dark); 229 230 // Set permissions view 231 setPermissions(mPermsList); 232 return mPermsView; 233 } 234 235 /** 236 * Canonicalizes the group description before it is displayed to the user. 237 * 238 * TODO check for internationalization issues remove trailing '.' in str1 239 */ 240 private String canonicalizeGroupDesc(String groupDesc) { 241 if ((groupDesc == null) || (groupDesc.length() == 0)) { 242 return null; 243 } 244 // Both str1 and str2 are non-null and are non-zero in size. 245 int len = groupDesc.length(); 246 if(groupDesc.charAt(len-1) == '.') { 247 groupDesc = groupDesc.substring(0, len-1); 248 } 249 return groupDesc; 250 } 251 252 /** 253 * Utility method that concatenates two strings defined by mPermFormat. 254 * a null value is returned if both str1 and str2 are null, if one of the strings 255 * is null the other non null value is returned without formatting 256 * this is to placate initial error checks 257 */ 258 private String formatPermissions(String groupDesc, CharSequence permDesc) { 259 if(groupDesc == null) { 260 if(permDesc == null) { 261 return null; 262 } 263 return permDesc.toString(); 264 } 265 groupDesc = canonicalizeGroupDesc(groupDesc); 266 if(permDesc == null) { 267 return groupDesc; 268 } 269 // groupDesc and permDesc are non null 270 return String.format(mPermFormat, groupDesc, permDesc.toString()); 271 } 272 273 private CharSequence getGroupLabel(String grpName) { 274 if (grpName == null) { 275 //return default label 276 return mDefaultGrpLabel; 277 } 278 CharSequence cachedLabel = mGroupLabelCache.get(grpName); 279 if (cachedLabel != null) { 280 return cachedLabel; 281 } 282 PermissionGroupInfo pgi; 283 try { 284 pgi = mPm.getPermissionGroupInfo(grpName, 0); 285 } catch (NameNotFoundException e) { 286 Log.i(TAG, "Invalid group name:" + grpName); 287 return null; 288 } 289 CharSequence label = pgi.loadLabel(mPm).toString(); 290 mGroupLabelCache.put(grpName, label); 291 return label; 292 } 293 294 /** 295 * Utility method that displays permissions from a map containing group name and 296 * list of permission descriptions. 297 */ 298 private void displayPermissions(boolean dangerous) { 299 Map<String, String> permInfoMap = dangerous ? mDangerousMap : mNormalMap; 300 LinearLayout permListView = dangerous ? mDangerousList : mNonDangerousList; 301 permListView.removeAllViews(); 302 303 Set<String> permInfoStrSet = permInfoMap.keySet(); 304 for (String loopPermGrpInfoStr : permInfoStrSet) { 305 CharSequence grpLabel = getGroupLabel(loopPermGrpInfoStr); 306 //guaranteed that grpLabel wont be null since permissions without groups 307 //will belong to the default group 308 if(localLOGV) Log.i(TAG, "Adding view group:" + grpLabel + ", desc:" 309 + permInfoMap.get(loopPermGrpInfoStr)); 310 permListView.addView(getPermissionItemView(grpLabel, 311 permInfoMap.get(loopPermGrpInfoStr), dangerous)); 312 } 313 } 314 315 private void displayNoPermissions() { 316 mNoPermsView.setVisibility(View.VISIBLE); 317 } 318 319 private View getPermissionItemView(CharSequence grpName, CharSequence permList, 320 boolean dangerous) { 321 return getPermissionItemView(mContext, mInflater, grpName, permList, 322 dangerous, dangerous ? mDangerousIcon : mNormalIcon); 323 } 324 325 private static View getPermissionItemView(Context context, LayoutInflater inflater, 326 CharSequence grpName, CharSequence permList, boolean dangerous, Drawable icon) { 327 View permView = inflater.inflate(R.layout.app_permission_item, null); 328 329 TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group); 330 TextView permDescView = (TextView) permView.findViewById(R.id.permission_list); 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