1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.packageinstaller.permission.ui; 18 19 import static android.content.pm.PackageManager.PERMISSION_DENIED; 20 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 21 22 import android.app.admin.DevicePolicyManager; 23 import android.content.Intent; 24 import android.content.pm.PackageInfo; 25 import android.content.pm.PackageManager; 26 import android.content.pm.PackageManager.NameNotFoundException; 27 import android.content.pm.PackageParser; 28 import android.content.pm.PermissionInfo; 29 import android.content.res.Configuration; 30 import android.content.res.Resources; 31 import android.graphics.drawable.Icon; 32 import android.os.Build; 33 import android.os.Bundle; 34 import android.text.Html; 35 import android.text.Spanned; 36 import android.util.Log; 37 import android.view.KeyEvent; 38 import android.view.MotionEvent; 39 import android.view.View; 40 import android.view.Window; 41 import android.view.WindowManager; 42 43 import com.android.packageinstaller.DeviceUtils; 44 import com.android.packageinstaller.R; 45 import com.android.packageinstaller.permission.model.AppPermissionGroup; 46 import com.android.packageinstaller.permission.model.AppPermissions; 47 import com.android.packageinstaller.permission.model.Permission; 48 import com.android.packageinstaller.permission.ui.auto.GrantPermissionsAutoViewHandler; 49 import com.android.packageinstaller.permission.ui.handheld.GrantPermissionsViewHandlerImpl; 50 import com.android.packageinstaller.permission.utils.ArrayUtils; 51 import com.android.packageinstaller.permission.utils.EventLogger; 52 import com.android.packageinstaller.permission.utils.SafetyNetLogger; 53 54 import java.util.ArrayList; 55 import java.util.Arrays; 56 import java.util.LinkedHashMap; 57 import java.util.List; 58 59 public class GrantPermissionsActivity extends OverlayTouchActivity 60 implements GrantPermissionsViewHandler.ResultListener { 61 62 private static final String LOG_TAG = "GrantPermissionsActivity"; 63 64 private String[] mRequestedPermissions; 65 private int[] mGrantResults; 66 67 private LinkedHashMap<String, GroupState> mRequestGrantPermissionGroups = new LinkedHashMap<>(); 68 69 private GrantPermissionsViewHandler mViewHandler; 70 private AppPermissions mAppPermissions; 71 72 boolean mResultSet; 73 74 @Override 75 public void onCreate(Bundle icicle) { 76 super.onCreate(icicle); 77 setFinishOnTouchOutside(false); 78 79 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); 80 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); 81 82 setTitle(R.string.permission_request_title); 83 84 if (DeviceUtils.isTelevision(this)) { 85 mViewHandler = new com.android.packageinstaller.permission.ui.television 86 .GrantPermissionsViewHandlerImpl(this, 87 getCallingPackage()).setResultListener(this); 88 } else if (DeviceUtils.isWear(this)) { 89 mViewHandler = new GrantPermissionsWatchViewHandler(this).setResultListener(this); 90 } else if (DeviceUtils.isAuto(this)) { 91 mViewHandler = new GrantPermissionsAutoViewHandler(this, getCallingPackage()) 92 .setResultListener(this); 93 } else { 94 mViewHandler = new com.android.packageinstaller.permission.ui.handheld 95 .GrantPermissionsViewHandlerImpl(this, getCallingPackage()) 96 .setResultListener(this); 97 } 98 99 mRequestedPermissions = getIntent().getStringArrayExtra( 100 PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES); 101 if (mRequestedPermissions == null) { 102 mRequestedPermissions = new String[0]; 103 } 104 105 final int requestedPermCount = mRequestedPermissions.length; 106 mGrantResults = new int[requestedPermCount]; 107 Arrays.fill(mGrantResults, PackageManager.PERMISSION_DENIED); 108 109 if (requestedPermCount == 0) { 110 setResultAndFinish(); 111 return; 112 } 113 114 PackageInfo callingPackageInfo = getCallingPackageInfo(); 115 116 if (callingPackageInfo == null || callingPackageInfo.requestedPermissions == null 117 || callingPackageInfo.requestedPermissions.length <= 0) { 118 setResultAndFinish(); 119 return; 120 } 121 122 // Don't allow legacy apps to request runtime permissions. 123 if (callingPackageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { 124 // Returning empty arrays means a cancellation. 125 mRequestedPermissions = new String[0]; 126 mGrantResults = new int[0]; 127 setResultAndFinish(); 128 return; 129 } 130 131 DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class); 132 final int permissionPolicy = devicePolicyManager.getPermissionPolicy(null); 133 134 // If calling package is null we default to deny all. 135 updateDefaultResults(callingPackageInfo, permissionPolicy); 136 137 mAppPermissions = new AppPermissions(this, callingPackageInfo, null, false, 138 new Runnable() { 139 @Override 140 public void run() { 141 setResultAndFinish(); 142 } 143 }); 144 145 for (String requestedPermission : mRequestedPermissions) { 146 AppPermissionGroup group = null; 147 for (AppPermissionGroup nextGroup : mAppPermissions.getPermissionGroups()) { 148 if (nextGroup.hasPermission(requestedPermission)) { 149 group = nextGroup; 150 break; 151 } 152 } 153 if (group == null) { 154 continue; 155 } 156 if (!group.isGrantingAllowed()) { 157 // Skip showing groups that we know cannot be granted. 158 continue; 159 } 160 // We allow the user to choose only non-fixed permissions. A permission 161 // is fixed either by device policy or the user denying with prejudice. 162 if (!group.isUserFixed() && !group.isPolicyFixed()) { 163 switch (permissionPolicy) { 164 case DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT: { 165 if (!group.areRuntimePermissionsGranted()) { 166 group.grantRuntimePermissions(false, computeAffectedPermissions( 167 callingPackageInfo, requestedPermission)); 168 } 169 group.setPolicyFixed(); 170 } break; 171 172 case DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY: { 173 if (group.areRuntimePermissionsGranted()) { 174 group.revokeRuntimePermissions(false, computeAffectedPermissions( 175 callingPackageInfo, requestedPermission)); 176 } 177 group.setPolicyFixed(); 178 } break; 179 180 default: { 181 if (!group.areRuntimePermissionsGranted()) { 182 GroupState state = mRequestGrantPermissionGroups.get(group.getName()); 183 if (state == null) { 184 state = new GroupState(group); 185 mRequestGrantPermissionGroups.put(group.getName(), state); 186 } 187 String[] affectedPermissions = computeAffectedPermissions( 188 callingPackageInfo, requestedPermission); 189 if (affectedPermissions != null) { 190 for (String affectedPermission : affectedPermissions) { 191 state.affectedPermissions = ArrayUtils.appendString( 192 state.affectedPermissions, affectedPermission); 193 } 194 } 195 } else { 196 group.grantRuntimePermissions(false, computeAffectedPermissions( 197 callingPackageInfo, requestedPermission)); 198 updateGrantResults(group); 199 } 200 } break; 201 } 202 } else { 203 // if the permission is fixed, ensure that we return the right request result 204 updateGrantResults(group); 205 } 206 } 207 208 setContentView(mViewHandler.createView()); 209 210 Window window = getWindow(); 211 WindowManager.LayoutParams layoutParams = window.getAttributes(); 212 mViewHandler.updateWindowAttributes(layoutParams); 213 window.setAttributes(layoutParams); 214 215 if (!showNextPermissionGroupGrantRequest()) { 216 setResultAndFinish(); 217 } else if (icicle == null) { 218 int numRequestedPermissions = mRequestedPermissions.length; 219 for (int permissionNum = 0; permissionNum < numRequestedPermissions; permissionNum++) { 220 String permission = mRequestedPermissions[permissionNum]; 221 222 EventLogger.logPermissionRequested(this, permission, 223 mAppPermissions.getPackageInfo().packageName); 224 } 225 } 226 } 227 228 @Override 229 public void onConfigurationChanged(Configuration newConfig) { 230 super.onConfigurationChanged(newConfig); 231 // We need to relayout the window as dialog width may be 232 // different in landscape vs portrait which affect the min 233 // window height needed to show all content. We have to 234 // re-add the window to force it to be resized if needed. 235 View decor = getWindow().getDecorView(); 236 if (decor.getParent() != null) { 237 getWindowManager().removeViewImmediate(decor); 238 getWindowManager().addView(decor, decor.getLayoutParams()); 239 if (mViewHandler instanceof GrantPermissionsViewHandlerImpl) { 240 ((GrantPermissionsViewHandlerImpl) mViewHandler).onConfigurationChanged(); 241 } 242 } 243 } 244 245 @Override 246 public boolean dispatchTouchEvent(MotionEvent ev) { 247 View rootView = getWindow().getDecorView(); 248 if (rootView.getTop() != 0) { 249 // We are animating the top view, need to compensate for that in motion events. 250 ev.setLocation(ev.getX(), ev.getY() - rootView.getTop()); 251 } 252 return super.dispatchTouchEvent(ev); 253 } 254 255 @Override 256 protected void onSaveInstanceState(Bundle outState) { 257 super.onSaveInstanceState(outState); 258 mViewHandler.saveInstanceState(outState); 259 } 260 261 @Override 262 protected void onRestoreInstanceState(Bundle savedInstanceState) { 263 super.onRestoreInstanceState(savedInstanceState); 264 mViewHandler.loadInstanceState(savedInstanceState); 265 } 266 267 private boolean showNextPermissionGroupGrantRequest() { 268 final int groupCount = mRequestGrantPermissionGroups.size(); 269 270 int currentIndex = 0; 271 for (GroupState groupState : mRequestGrantPermissionGroups.values()) { 272 if (groupState.mState == GroupState.STATE_UNKNOWN) { 273 CharSequence appLabel = mAppPermissions.getAppLabel(); 274 Spanned message = Html.fromHtml(getString(R.string.permission_warning_template, 275 appLabel, groupState.mGroup.getDescription()), 0); 276 // Set the permission message as the title so it can be announced. 277 setTitle(message); 278 279 // Set the new grant view 280 // TODO: Use a real message for the action. We need group action APIs 281 Resources resources; 282 try { 283 resources = getPackageManager().getResourcesForApplication( 284 groupState.mGroup.getIconPkg()); 285 } catch (NameNotFoundException e) { 286 // Fallback to system. 287 resources = Resources.getSystem(); 288 } 289 int icon = groupState.mGroup.getIconResId(); 290 291 mViewHandler.updateUi(groupState.mGroup.getName(), groupCount, currentIndex, 292 Icon.createWithResource(resources, icon), message, 293 groupState.mGroup.isUserSet()); 294 return true; 295 } 296 297 currentIndex++; 298 } 299 300 return false; 301 } 302 303 @Override 304 public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) { 305 GroupState groupState = mRequestGrantPermissionGroups.get(name); 306 if (groupState.mGroup != null) { 307 if (granted) { 308 groupState.mGroup.grantRuntimePermissions(doNotAskAgain, 309 groupState.affectedPermissions); 310 groupState.mState = GroupState.STATE_ALLOWED; 311 } else { 312 groupState.mGroup.revokeRuntimePermissions(doNotAskAgain, 313 groupState.affectedPermissions); 314 groupState.mState = GroupState.STATE_DENIED; 315 316 int numRequestedPermissions = mRequestedPermissions.length; 317 for (int i = 0; i < numRequestedPermissions; i++) { 318 String permission = mRequestedPermissions[i]; 319 320 if (groupState.mGroup.hasPermission(permission)) { 321 EventLogger.logPermissionDenied(this, permission, 322 mAppPermissions.getPackageInfo().packageName); 323 } 324 } 325 } 326 updateGrantResults(groupState.mGroup); 327 } 328 if (!showNextPermissionGroupGrantRequest()) { 329 setResultAndFinish(); 330 } 331 } 332 333 private void updateGrantResults(AppPermissionGroup group) { 334 for (Permission permission : group.getPermissions()) { 335 final int index = ArrayUtils.indexOf( 336 mRequestedPermissions, permission.getName()); 337 if (index >= 0) { 338 mGrantResults[index] = permission.isGranted() ? PackageManager.PERMISSION_GRANTED 339 : PackageManager.PERMISSION_DENIED; 340 } 341 } 342 } 343 344 @Override 345 public boolean onKeyDown(int keyCode, KeyEvent event) { 346 // We do not allow backing out. 347 return keyCode == KeyEvent.KEYCODE_BACK; 348 } 349 350 @Override 351 public boolean onKeyUp(int keyCode, KeyEvent event) { 352 // We do not allow backing out. 353 return keyCode == KeyEvent.KEYCODE_BACK; 354 } 355 356 @Override 357 public void finish() { 358 setResultIfNeeded(RESULT_CANCELED); 359 super.finish(); 360 } 361 362 private int computePermissionGrantState(PackageInfo callingPackageInfo, 363 String permission, int permissionPolicy) { 364 boolean permissionRequested = false; 365 366 for (int i = 0; i < callingPackageInfo.requestedPermissions.length; i++) { 367 if (permission.equals(callingPackageInfo.requestedPermissions[i])) { 368 permissionRequested = true; 369 if ((callingPackageInfo.requestedPermissionsFlags[i] 370 & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) { 371 return PERMISSION_GRANTED; 372 } 373 break; 374 } 375 } 376 377 if (!permissionRequested) { 378 return PERMISSION_DENIED; 379 } 380 381 try { 382 PermissionInfo pInfo = getPackageManager().getPermissionInfo(permission, 0); 383 if ((pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) 384 != PermissionInfo.PROTECTION_DANGEROUS) { 385 return PERMISSION_DENIED; 386 } 387 if ((pInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) == 0 388 && callingPackageInfo.applicationInfo.isInstantApp()) { 389 return PERMISSION_DENIED; 390 } 391 if ((pInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0 392 && callingPackageInfo.applicationInfo.targetSdkVersion 393 < Build.VERSION_CODES.M) { 394 return PERMISSION_DENIED; 395 } 396 } catch (NameNotFoundException e) { 397 return PERMISSION_DENIED; 398 } 399 400 switch (permissionPolicy) { 401 case DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT: { 402 return PERMISSION_GRANTED; 403 } 404 default: { 405 return PERMISSION_DENIED; 406 } 407 } 408 } 409 410 private PackageInfo getCallingPackageInfo() { 411 try { 412 return getPackageManager().getPackageInfo(getCallingPackage(), 413 PackageManager.GET_PERMISSIONS); 414 } catch (NameNotFoundException e) { 415 Log.i(LOG_TAG, "No package: " + getCallingPackage(), e); 416 return null; 417 } 418 } 419 420 private void updateDefaultResults(PackageInfo callingPackageInfo, int permissionPolicy) { 421 final int requestedPermCount = mRequestedPermissions.length; 422 for (int i = 0; i < requestedPermCount; i++) { 423 String permission = mRequestedPermissions[i]; 424 mGrantResults[i] = callingPackageInfo != null 425 ? computePermissionGrantState(callingPackageInfo, permission, permissionPolicy) 426 : PERMISSION_DENIED; 427 } 428 } 429 430 private void setResultIfNeeded(int resultCode) { 431 if (!mResultSet) { 432 mResultSet = true; 433 logRequestedPermissionGroups(); 434 Intent result = new Intent(PackageManager.ACTION_REQUEST_PERMISSIONS); 435 result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, mRequestedPermissions); 436 result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, mGrantResults); 437 setResult(resultCode, result); 438 } 439 } 440 441 private void setResultAndFinish() { 442 setResultIfNeeded(RESULT_OK); 443 finish(); 444 } 445 446 private void logRequestedPermissionGroups() { 447 if (mRequestGrantPermissionGroups.isEmpty()) { 448 return; 449 } 450 451 final int groupCount = mRequestGrantPermissionGroups.size(); 452 List<AppPermissionGroup> groups = new ArrayList<>(groupCount); 453 for (GroupState groupState : mRequestGrantPermissionGroups.values()) { 454 groups.add(groupState.mGroup); 455 } 456 457 SafetyNetLogger.logPermissionsRequested(mAppPermissions.getPackageInfo(), groups); 458 } 459 460 private static String[] computeAffectedPermissions(PackageInfo callingPkg, 461 String permission) { 462 // For <= N_MR1 apps all permissions are affected. 463 if (callingPkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) { 464 return null; 465 } 466 467 // For N_MR1+ apps only the requested permission is affected with addition 468 // to splits of this permission applicable to apps targeting N_MR1. 469 String[] permissions = new String[] {permission}; 470 for (PackageParser.SplitPermissionInfo splitPerm : PackageParser.SPLIT_PERMISSIONS) { 471 if (splitPerm.targetSdk <= Build.VERSION_CODES.N_MR1 472 || callingPkg.applicationInfo.targetSdkVersion >= splitPerm.targetSdk 473 || !permission.equals(splitPerm.rootPerm)) { 474 continue; 475 } 476 for (int i = 0; i < splitPerm.newPerms.length; i++) { 477 final String newPerm = splitPerm.newPerms[i]; 478 permissions = ArrayUtils.appendString(permissions, newPerm); 479 } 480 } 481 482 return permissions; 483 } 484 485 private static final class GroupState { 486 static final int STATE_UNKNOWN = 0; 487 static final int STATE_ALLOWED = 1; 488 static final int STATE_DENIED = 2; 489 490 final AppPermissionGroup mGroup; 491 int mState = STATE_UNKNOWN; 492 String[] affectedPermissions; 493 494 GroupState(AppPermissionGroup group) { 495 mGroup = group; 496 } 497 } 498 } 499