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.PermissionInfo; 28 import android.content.res.Configuration; 29 import android.content.res.Resources; 30 import android.graphics.drawable.Icon; 31 import android.hardware.camera2.utils.ArrayUtils; 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.handheld.GrantPermissionsViewHandlerImpl; 49 import com.android.packageinstaller.permission.utils.SafetyNetLogger; 50 51 import java.util.ArrayList; 52 import java.util.Arrays; 53 import java.util.LinkedHashMap; 54 import java.util.List; 55 56 public class GrantPermissionsActivity extends OverlayTouchActivity 57 implements GrantPermissionsViewHandler.ResultListener { 58 59 private static final String LOG_TAG = "GrantPermissionsActivity"; 60 61 private String[] mRequestedPermissions; 62 private int[] mGrantResults; 63 64 private LinkedHashMap<String, GroupState> mRequestGrantPermissionGroups = new LinkedHashMap<>(); 65 66 private GrantPermissionsViewHandler mViewHandler; 67 private AppPermissions mAppPermissions; 68 69 boolean mResultSet; 70 71 @Override onCreate(Bundle icicle)72 public void onCreate(Bundle icicle) { 73 super.onCreate(icicle); 74 setFinishOnTouchOutside(false); 75 76 setTitle(R.string.permission_request_title); 77 78 if (DeviceUtils.isTelevision(this)) { 79 mViewHandler = new com.android.packageinstaller.permission.ui.television 80 .GrantPermissionsViewHandlerImpl(this).setResultListener(this); 81 } else if (DeviceUtils.isWear(this)) { 82 mViewHandler = new GrantPermissionsWatchViewHandler(this).setResultListener(this); 83 } else { 84 mViewHandler = new com.android.packageinstaller.permission.ui.handheld 85 .GrantPermissionsViewHandlerImpl(this).setResultListener(this); 86 } 87 88 mRequestedPermissions = getIntent().getStringArrayExtra( 89 PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES); 90 if (mRequestedPermissions == null) { 91 mRequestedPermissions = new String[0]; 92 } 93 94 final int requestedPermCount = mRequestedPermissions.length; 95 mGrantResults = new int[requestedPermCount]; 96 Arrays.fill(mGrantResults, PackageManager.PERMISSION_DENIED); 97 98 if (requestedPermCount == 0) { 99 setResultAndFinish(); 100 return; 101 } 102 103 PackageInfo callingPackageInfo = getCallingPackageInfo(); 104 105 if (callingPackageInfo == null || callingPackageInfo.requestedPermissions == null 106 || callingPackageInfo.requestedPermissions.length <= 0) { 107 setResultAndFinish(); 108 return; 109 } 110 111 // Don't allow legacy apps to request runtime permissions. 112 if (callingPackageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) { 113 // Returning empty arrays means a cancellation. 114 mRequestedPermissions = new String[0]; 115 mGrantResults = new int[0]; 116 setResultAndFinish(); 117 return; 118 } 119 120 DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class); 121 final int permissionPolicy = devicePolicyManager.getPermissionPolicy(null); 122 123 // If calling package is null we default to deny all. 124 updateDefaultResults(callingPackageInfo, permissionPolicy); 125 126 mAppPermissions = new AppPermissions(this, callingPackageInfo, null, false, 127 new Runnable() { 128 @Override 129 public void run() { 130 setResultAndFinish(); 131 } 132 }); 133 134 for (String requestedPermission : mRequestedPermissions) { 135 AppPermissionGroup group = null; 136 for (AppPermissionGroup nextGroup : mAppPermissions.getPermissionGroups()) { 137 if (nextGroup.hasPermission(requestedPermission)) { 138 group = nextGroup; 139 break; 140 } 141 } 142 if (group == null) { 143 continue; 144 } 145 // We allow the user to choose only non-fixed permissions. A permission 146 // is fixed either by device policy or the user denying with prejudice. 147 if (!group.isUserFixed() && !group.isPolicyFixed()) { 148 switch (permissionPolicy) { 149 case DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT: { 150 if (!group.areRuntimePermissionsGranted()) { 151 group.grantRuntimePermissions(false); 152 } 153 group.setPolicyFixed(); 154 } break; 155 156 case DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY: { 157 if (group.areRuntimePermissionsGranted()) { 158 group.revokeRuntimePermissions(false); 159 } 160 group.setPolicyFixed(); 161 } break; 162 163 default: { 164 if (!group.areRuntimePermissionsGranted()) { 165 mRequestGrantPermissionGroups.put(group.getName(), 166 new GroupState(group)); 167 } else { 168 group.grantRuntimePermissions(false); 169 updateGrantResults(group); 170 } 171 } break; 172 } 173 } else { 174 // if the permission is fixed, ensure that we return the right request result 175 updateGrantResults(group); 176 } 177 } 178 179 setContentView(mViewHandler.createView()); 180 181 Window window = getWindow(); 182 WindowManager.LayoutParams layoutParams = window.getAttributes(); 183 mViewHandler.updateWindowAttributes(layoutParams); 184 window.setAttributes(layoutParams); 185 186 if (!showNextPermissionGroupGrantRequest()) { 187 setResultAndFinish(); 188 } 189 } 190 191 @Override onConfigurationChanged(Configuration newConfig)192 public void onConfigurationChanged(Configuration newConfig) { 193 super.onConfigurationChanged(newConfig); 194 // We need to relayout the window as dialog width may be 195 // different in landscape vs portrait which affect the min 196 // window height needed to show all content. We have to 197 // re-add the window to force it to be resized if needed. 198 View decor = getWindow().getDecorView(); 199 getWindowManager().removeViewImmediate(decor); 200 getWindowManager().addView(decor, decor.getLayoutParams()); 201 if (mViewHandler instanceof GrantPermissionsViewHandlerImpl) { 202 ((GrantPermissionsViewHandlerImpl) mViewHandler).onConfigurationChanged(); 203 } 204 } 205 206 @Override dispatchTouchEvent(MotionEvent ev)207 public boolean dispatchTouchEvent(MotionEvent ev) { 208 View rootView = getWindow().getDecorView(); 209 if (rootView.getTop() != 0) { 210 // We are animating the top view, need to compensate for that in motion events. 211 ev.setLocation(ev.getX(), ev.getY() - rootView.getTop()); 212 } 213 return super.dispatchTouchEvent(ev); 214 } 215 216 @Override onSaveInstanceState(Bundle outState)217 protected void onSaveInstanceState(Bundle outState) { 218 super.onSaveInstanceState(outState); 219 mViewHandler.saveInstanceState(outState); 220 } 221 222 @Override onRestoreInstanceState(Bundle savedInstanceState)223 protected void onRestoreInstanceState(Bundle savedInstanceState) { 224 super.onRestoreInstanceState(savedInstanceState); 225 mViewHandler.loadInstanceState(savedInstanceState); 226 } 227 showNextPermissionGroupGrantRequest()228 private boolean showNextPermissionGroupGrantRequest() { 229 final int groupCount = mRequestGrantPermissionGroups.size(); 230 231 int currentIndex = 0; 232 for (GroupState groupState : mRequestGrantPermissionGroups.values()) { 233 if (groupState.mState == GroupState.STATE_UNKNOWN) { 234 CharSequence appLabel = mAppPermissions.getAppLabel(); 235 Spanned message = Html.fromHtml(getString(R.string.permission_warning_template, 236 appLabel, groupState.mGroup.getDescription()), 0); 237 // Set the permission message as the title so it can be announced. 238 setTitle(message); 239 240 // Set the new grant view 241 // TODO: Use a real message for the action. We need group action APIs 242 Resources resources; 243 try { 244 resources = getPackageManager().getResourcesForApplication( 245 groupState.mGroup.getIconPkg()); 246 } catch (NameNotFoundException e) { 247 // Fallback to system. 248 resources = Resources.getSystem(); 249 } 250 int icon = groupState.mGroup.getIconResId(); 251 252 mViewHandler.updateUi(groupState.mGroup.getName(), groupCount, currentIndex, 253 Icon.createWithResource(resources, icon), message, 254 groupState.mGroup.isUserSet()); 255 return true; 256 } 257 258 currentIndex++; 259 } 260 261 return false; 262 } 263 264 @Override onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain)265 public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) { 266 GroupState groupState = mRequestGrantPermissionGroups.get(name); 267 if (groupState.mGroup != null) { 268 if (granted) { 269 groupState.mGroup.grantRuntimePermissions(doNotAskAgain); 270 groupState.mState = GroupState.STATE_ALLOWED; 271 } else { 272 groupState.mGroup.revokeRuntimePermissions(doNotAskAgain); 273 groupState.mState = GroupState.STATE_DENIED; 274 } 275 updateGrantResults(groupState.mGroup); 276 } 277 if (!showNextPermissionGroupGrantRequest()) { 278 setResultAndFinish(); 279 } 280 } 281 updateGrantResults(AppPermissionGroup group)282 private void updateGrantResults(AppPermissionGroup group) { 283 for (Permission permission : group.getPermissions()) { 284 final int index = ArrayUtils.getArrayIndex( 285 mRequestedPermissions, permission.getName()); 286 if (index >= 0) { 287 mGrantResults[index] = permission.isGranted() ? PackageManager.PERMISSION_GRANTED 288 : PackageManager.PERMISSION_DENIED; 289 } 290 } 291 } 292 293 @Override onKeyDown(int keyCode, KeyEvent event)294 public boolean onKeyDown(int keyCode, KeyEvent event) { 295 // We do not allow backing out. 296 return keyCode == KeyEvent.KEYCODE_BACK; 297 } 298 299 @Override onKeyUp(int keyCode, KeyEvent event)300 public boolean onKeyUp(int keyCode, KeyEvent event) { 301 // We do not allow backing out. 302 return keyCode == KeyEvent.KEYCODE_BACK; 303 } 304 305 @Override finish()306 public void finish() { 307 setResultIfNeeded(RESULT_CANCELED); 308 super.finish(); 309 } 310 computePermissionGrantState(PackageInfo callingPackageInfo, String permission, int permissionPolicy)311 private int computePermissionGrantState(PackageInfo callingPackageInfo, 312 String permission, int permissionPolicy) { 313 boolean permissionRequested = false; 314 315 for (int i = 0; i < callingPackageInfo.requestedPermissions.length; i++) { 316 if (permission.equals(callingPackageInfo.requestedPermissions[i])) { 317 permissionRequested = true; 318 if ((callingPackageInfo.requestedPermissionsFlags[i] 319 & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) { 320 return PERMISSION_GRANTED; 321 } 322 break; 323 } 324 } 325 326 if (!permissionRequested) { 327 return PERMISSION_DENIED; 328 } 329 330 try { 331 PermissionInfo pInfo = getPackageManager().getPermissionInfo(permission, 0); 332 if ((pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) 333 != PermissionInfo.PROTECTION_DANGEROUS) { 334 return PERMISSION_DENIED; 335 } 336 } catch (NameNotFoundException e) { 337 return PERMISSION_DENIED; 338 } 339 340 switch (permissionPolicy) { 341 case DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT: { 342 return PERMISSION_GRANTED; 343 } 344 default: { 345 return PERMISSION_DENIED; 346 } 347 } 348 } 349 getCallingPackageInfo()350 private PackageInfo getCallingPackageInfo() { 351 try { 352 return getPackageManager().getPackageInfo(getCallingPackage(), 353 PackageManager.GET_PERMISSIONS); 354 } catch (NameNotFoundException e) { 355 Log.i(LOG_TAG, "No package: " + getCallingPackage(), e); 356 return null; 357 } 358 } 359 updateDefaultResults(PackageInfo callingPackageInfo, int permissionPolicy)360 private void updateDefaultResults(PackageInfo callingPackageInfo, int permissionPolicy) { 361 final int requestedPermCount = mRequestedPermissions.length; 362 for (int i = 0; i < requestedPermCount; i++) { 363 String permission = mRequestedPermissions[i]; 364 mGrantResults[i] = callingPackageInfo != null 365 ? computePermissionGrantState(callingPackageInfo, permission, permissionPolicy) 366 : PERMISSION_DENIED; 367 } 368 } 369 setResultIfNeeded(int resultCode)370 private void setResultIfNeeded(int resultCode) { 371 if (!mResultSet) { 372 mResultSet = true; 373 logRequestedPermissionGroups(); 374 Intent result = new Intent(PackageManager.ACTION_REQUEST_PERMISSIONS); 375 result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, mRequestedPermissions); 376 result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, mGrantResults); 377 setResult(resultCode, result); 378 } 379 } 380 setResultAndFinish()381 private void setResultAndFinish() { 382 setResultIfNeeded(RESULT_OK); 383 finish(); 384 } 385 logRequestedPermissionGroups()386 private void logRequestedPermissionGroups() { 387 if (mRequestGrantPermissionGroups.isEmpty()) { 388 return; 389 } 390 391 final int groupCount = mRequestGrantPermissionGroups.size(); 392 List<AppPermissionGroup> groups = new ArrayList<>(groupCount); 393 for (GroupState groupState : mRequestGrantPermissionGroups.values()) { 394 groups.add(groupState.mGroup); 395 } 396 397 SafetyNetLogger.logPermissionsRequested(mAppPermissions.getPackageInfo(), groups); 398 } 399 400 private static final class GroupState { 401 static final int STATE_UNKNOWN = 0; 402 static final int STATE_ALLOWED = 1; 403 static final int STATE_DENIED = 2; 404 405 final AppPermissionGroup mGroup; 406 int mState = STATE_UNKNOWN; 407 GroupState(AppPermissionGroup group)408 GroupState(AppPermissionGroup group) { 409 mGroup = group; 410 } 411 } 412 } 413