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 17package com.android.packageinstaller.permission.ui; 18 19import static android.content.pm.PackageManager.PERMISSION_DENIED; 20import static android.content.pm.PackageManager.PERMISSION_GRANTED; 21 22import android.app.admin.DevicePolicyManager; 23import android.content.Intent; 24import android.content.pm.PackageInfo; 25import android.content.pm.PackageManager; 26import android.content.pm.PackageManager.NameNotFoundException; 27import android.content.pm.PermissionInfo; 28import android.content.res.Configuration; 29import android.content.res.Resources; 30import android.graphics.drawable.Icon; 31import android.hardware.camera2.utils.ArrayUtils; 32import android.os.Build; 33import android.os.Bundle; 34import android.text.Html; 35import android.text.Spanned; 36import android.util.Log; 37import android.view.KeyEvent; 38import android.view.MotionEvent; 39import android.view.View; 40import android.view.Window; 41import android.view.WindowManager; 42 43import com.android.packageinstaller.DeviceUtils; 44import com.android.packageinstaller.R; 45import com.android.packageinstaller.permission.model.AppPermissionGroup; 46import com.android.packageinstaller.permission.model.AppPermissions; 47import com.android.packageinstaller.permission.model.Permission; 48import com.android.packageinstaller.permission.ui.handheld.GrantPermissionsViewHandlerImpl; 49import com.android.packageinstaller.permission.utils.SafetyNetLogger; 50 51import java.util.ArrayList; 52import java.util.Arrays; 53import java.util.LinkedHashMap; 54import java.util.List; 55 56public 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 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 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 if (decor.getParent() != null) { 200 getWindowManager().removeViewImmediate(decor); 201 getWindowManager().addView(decor, decor.getLayoutParams()); 202 if (mViewHandler instanceof GrantPermissionsViewHandlerImpl) { 203 ((GrantPermissionsViewHandlerImpl) mViewHandler).onConfigurationChanged(); 204 } 205 } 206 } 207 208 @Override 209 public boolean dispatchTouchEvent(MotionEvent ev) { 210 View rootView = getWindow().getDecorView(); 211 if (rootView.getTop() != 0) { 212 // We are animating the top view, need to compensate for that in motion events. 213 ev.setLocation(ev.getX(), ev.getY() - rootView.getTop()); 214 } 215 return super.dispatchTouchEvent(ev); 216 } 217 218 @Override 219 protected void onSaveInstanceState(Bundle outState) { 220 super.onSaveInstanceState(outState); 221 mViewHandler.saveInstanceState(outState); 222 } 223 224 @Override 225 protected void onRestoreInstanceState(Bundle savedInstanceState) { 226 super.onRestoreInstanceState(savedInstanceState); 227 mViewHandler.loadInstanceState(savedInstanceState); 228 } 229 230 private boolean showNextPermissionGroupGrantRequest() { 231 final int groupCount = mRequestGrantPermissionGroups.size(); 232 233 int currentIndex = 0; 234 for (GroupState groupState : mRequestGrantPermissionGroups.values()) { 235 if (groupState.mState == GroupState.STATE_UNKNOWN) { 236 CharSequence appLabel = mAppPermissions.getAppLabel(); 237 Spanned message = Html.fromHtml(getString(R.string.permission_warning_template, 238 appLabel, groupState.mGroup.getDescription()), 0); 239 // Set the permission message as the title so it can be announced. 240 setTitle(message); 241 242 // Set the new grant view 243 // TODO: Use a real message for the action. We need group action APIs 244 Resources resources; 245 try { 246 resources = getPackageManager().getResourcesForApplication( 247 groupState.mGroup.getIconPkg()); 248 } catch (NameNotFoundException e) { 249 // Fallback to system. 250 resources = Resources.getSystem(); 251 } 252 int icon = groupState.mGroup.getIconResId(); 253 254 mViewHandler.updateUi(groupState.mGroup.getName(), groupCount, currentIndex, 255 Icon.createWithResource(resources, icon), message, 256 groupState.mGroup.isUserSet()); 257 return true; 258 } 259 260 currentIndex++; 261 } 262 263 return false; 264 } 265 266 @Override 267 public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) { 268 GroupState groupState = mRequestGrantPermissionGroups.get(name); 269 if (groupState.mGroup != null) { 270 if (granted) { 271 groupState.mGroup.grantRuntimePermissions(doNotAskAgain); 272 groupState.mState = GroupState.STATE_ALLOWED; 273 } else { 274 groupState.mGroup.revokeRuntimePermissions(doNotAskAgain); 275 groupState.mState = GroupState.STATE_DENIED; 276 } 277 updateGrantResults(groupState.mGroup); 278 } 279 if (!showNextPermissionGroupGrantRequest()) { 280 setResultAndFinish(); 281 } 282 } 283 284 private void updateGrantResults(AppPermissionGroup group) { 285 for (Permission permission : group.getPermissions()) { 286 final int index = ArrayUtils.getArrayIndex( 287 mRequestedPermissions, permission.getName()); 288 if (index >= 0) { 289 mGrantResults[index] = permission.isGranted() ? PackageManager.PERMISSION_GRANTED 290 : PackageManager.PERMISSION_DENIED; 291 } 292 } 293 } 294 295 @Override 296 public boolean onKeyDown(int keyCode, KeyEvent event) { 297 // We do not allow backing out. 298 return keyCode == KeyEvent.KEYCODE_BACK; 299 } 300 301 @Override 302 public boolean onKeyUp(int keyCode, KeyEvent event) { 303 // We do not allow backing out. 304 return keyCode == KeyEvent.KEYCODE_BACK; 305 } 306 307 @Override 308 public void finish() { 309 setResultIfNeeded(RESULT_CANCELED); 310 super.finish(); 311 } 312 313 private int computePermissionGrantState(PackageInfo callingPackageInfo, 314 String permission, int permissionPolicy) { 315 boolean permissionRequested = false; 316 317 for (int i = 0; i < callingPackageInfo.requestedPermissions.length; i++) { 318 if (permission.equals(callingPackageInfo.requestedPermissions[i])) { 319 permissionRequested = true; 320 if ((callingPackageInfo.requestedPermissionsFlags[i] 321 & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) { 322 return PERMISSION_GRANTED; 323 } 324 break; 325 } 326 } 327 328 if (!permissionRequested) { 329 return PERMISSION_DENIED; 330 } 331 332 try { 333 PermissionInfo pInfo = getPackageManager().getPermissionInfo(permission, 0); 334 if ((pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) 335 != PermissionInfo.PROTECTION_DANGEROUS) { 336 return PERMISSION_DENIED; 337 } 338 } catch (NameNotFoundException e) { 339 return PERMISSION_DENIED; 340 } 341 342 switch (permissionPolicy) { 343 case DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT: { 344 return PERMISSION_GRANTED; 345 } 346 default: { 347 return PERMISSION_DENIED; 348 } 349 } 350 } 351 352 private PackageInfo getCallingPackageInfo() { 353 try { 354 return getPackageManager().getPackageInfo(getCallingPackage(), 355 PackageManager.GET_PERMISSIONS); 356 } catch (NameNotFoundException e) { 357 Log.i(LOG_TAG, "No package: " + getCallingPackage(), e); 358 return null; 359 } 360 } 361 362 private void updateDefaultResults(PackageInfo callingPackageInfo, int permissionPolicy) { 363 final int requestedPermCount = mRequestedPermissions.length; 364 for (int i = 0; i < requestedPermCount; i++) { 365 String permission = mRequestedPermissions[i]; 366 mGrantResults[i] = callingPackageInfo != null 367 ? computePermissionGrantState(callingPackageInfo, permission, permissionPolicy) 368 : PERMISSION_DENIED; 369 } 370 } 371 372 private void setResultIfNeeded(int resultCode) { 373 if (!mResultSet) { 374 mResultSet = true; 375 logRequestedPermissionGroups(); 376 Intent result = new Intent(PackageManager.ACTION_REQUEST_PERMISSIONS); 377 result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, mRequestedPermissions); 378 result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, mGrantResults); 379 setResult(resultCode, result); 380 } 381 } 382 383 private void setResultAndFinish() { 384 setResultIfNeeded(RESULT_OK); 385 finish(); 386 } 387 388 private void logRequestedPermissionGroups() { 389 if (mRequestGrantPermissionGroups.isEmpty()) { 390 return; 391 } 392 393 final int groupCount = mRequestGrantPermissionGroups.size(); 394 List<AppPermissionGroup> groups = new ArrayList<>(groupCount); 395 for (GroupState groupState : mRequestGrantPermissionGroups.values()) { 396 groups.add(groupState.mGroup); 397 } 398 399 SafetyNetLogger.logPermissionsRequested(mAppPermissions.getPackageInfo(), groups); 400 } 401 402 private static final class GroupState { 403 static final int STATE_UNKNOWN = 0; 404 static final int STATE_ALLOWED = 1; 405 static final int STATE_DENIED = 2; 406 407 final AppPermissionGroup mGroup; 408 int mState = STATE_UNKNOWN; 409 410 GroupState(AppPermissionGroup group) { 411 mGroup = group; 412 } 413 } 414} 415