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