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.permissioncontroller.permission.ui; 18 19 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 20 21 import static com.android.permissioncontroller.Constants.ACTION_MANAGE_AUTO_REVOKE; 22 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID; 23 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID; 24 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION; 25 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__OPENED_FOR_AUTO_REVOKE; 26 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__OPENED_FROM_INTENT; 27 import static com.android.permissioncontroller.PermissionControllerStatsLog.AUTO_REVOKE_NOTIFICATION_CLICKED; 28 import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_USAGE_FRAGMENT_INTERACTION; 29 import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__OPEN; 30 31 import android.Manifest; 32 import android.app.ActionBar; 33 import android.content.Intent; 34 import android.content.pm.PackageManager; 35 import android.content.pm.PermissionInfo; 36 import android.os.Build; 37 import android.os.Bundle; 38 import android.os.Process; 39 import android.os.UserHandle; 40 import android.permission.PermissionManager; 41 import android.provider.Settings; 42 import android.util.Log; 43 import android.view.MenuItem; 44 45 import androidx.fragment.app.Fragment; 46 import androidx.navigation.NavGraph; 47 import androidx.navigation.NavInflater; 48 import androidx.navigation.Navigation; 49 import androidx.navigation.fragment.NavHostFragment; 50 51 import com.android.permissioncontroller.Constants; 52 import com.android.permissioncontroller.DeviceUtils; 53 import com.android.permissioncontroller.PermissionControllerStatsLog; 54 import com.android.permissioncontroller.R; 55 import com.android.permissioncontroller.permission.ui.auto.AutoAllAppPermissionsFragment; 56 import com.android.permissioncontroller.permission.ui.auto.AutoAppPermissionsFragment; 57 import com.android.permissioncontroller.permission.ui.auto.AutoManageStandardPermissionsFragment; 58 import com.android.permissioncontroller.permission.ui.auto.AutoPermissionAppsFragment; 59 import com.android.permissioncontroller.permission.ui.auto.AutoReviewPermissionDecisionsFragment; 60 import com.android.permissioncontroller.permission.ui.auto.AutoUnusedAppsFragment; 61 import com.android.permissioncontroller.permission.ui.auto.dashboard.AutoPermissionUsageDetailsFragment; 62 import com.android.permissioncontroller.permission.ui.auto.dashboard.AutoPermissionUsageFragment; 63 import com.android.permissioncontroller.permission.ui.handheld.AppPermissionFragment; 64 import com.android.permissioncontroller.permission.ui.handheld.AppPermissionGroupsFragment; 65 import com.android.permissioncontroller.permission.ui.handheld.HandheldUnusedAppsWrapperFragment; 66 import com.android.permissioncontroller.permission.ui.handheld.PermissionAppsFragment; 67 import com.android.permissioncontroller.permission.ui.handheld.v31.PermissionDetailsWrapperFragment; 68 import com.android.permissioncontroller.permission.ui.handheld.v31.PermissionUsageV2WrapperFragment; 69 import com.android.permissioncontroller.permission.ui.legacy.AppPermissionActivity; 70 import com.android.permissioncontroller.permission.ui.television.TvUnusedAppsFragment; 71 import com.android.permissioncontroller.permission.ui.wear.AppPermissionsFragmentWear; 72 import com.android.permissioncontroller.permission.utils.KotlinUtils; 73 import com.android.permissioncontroller.permission.utils.Utils; 74 75 import java.util.Random; 76 77 /** 78 * Activity to review and manage permissions 79 */ 80 public final class ManagePermissionsActivity extends SettingsActivity { 81 private static final String LOG_TAG = ManagePermissionsActivity.class.getSimpleName(); 82 83 /** 84 * Name of the extra parameter that indicates whether or not to show all app permissions 85 */ 86 public static final String EXTRA_ALL_PERMISSIONS = 87 "com.android.permissioncontroller.extra.ALL_PERMISSIONS"; 88 89 /** 90 * Name of the extra parameter that is the fragment that called the current fragment. 91 */ 92 public static final String EXTRA_CALLER_NAME = 93 "com.android.permissioncontroller.extra.CALLER_NAME"; 94 95 // The permission group which was interacted with 96 public static final String EXTRA_RESULT_PERMISSION_INTERACTED = "com.android" 97 + ".permissioncontroller.extra.RESULT_PERMISSION_INTERACTED"; 98 /** 99 * The result of the permission in terms of {@link GrantPermissionsViewHandler.Result} 100 */ 101 public static final String EXTRA_RESULT_PERMISSION_RESULT = "com.android" 102 + ".permissioncontroller.extra.PERMISSION_RESULT"; 103 104 /** 105 * Whether to show system apps in UI receiving an intent containing this extra. 106 */ 107 public static final String EXTRA_SHOW_SYSTEM = "com.android" 108 + ".permissioncontroller.extra.SHOW_SYSTEM"; 109 110 /** 111 * Whether to show 7 days permission usage data in UI receiving an intent containing this extra. 112 */ 113 public static final String EXTRA_SHOW_7_DAYS = "com.android" 114 + ".permissioncontroller.extra.SHOW_7_DAYS"; 115 116 /** 117 * The requestCode used when we decide not to use this activity, but instead launch 118 * another activity in our place. When that activity finishes, we set it's result 119 * as our result and then finish. 120 */ 121 private static final int PROXY_ACTIVITY_REQUEST_CODE = 5; 122 123 @Override onCreate(Bundle savedInstanceState)124 public void onCreate(Bundle savedInstanceState) { 125 if (DeviceUtils.isAuto(this)) { 126 // Automotive relies on a different theme. Apply before calling super so that 127 // fragments are restored properly on configuration changes. 128 setTheme(R.style.CarSettings); 129 } 130 super.onCreate(savedInstanceState); 131 132 // If this is not a phone (which uses the Navigation component), and there is a previous 133 // instance, re-use its Fragment instead of making a new one. 134 if ((DeviceUtils.isTelevision(this) || DeviceUtils.isAuto(this) 135 || DeviceUtils.isWear(this)) && savedInstanceState != null) { 136 return; 137 } 138 139 boolean provisioned = Settings.Global.getInt( 140 getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0; 141 boolean completed = Settings.Secure.getInt( 142 getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 0) != 0; 143 if (!provisioned || !completed) { 144 finishAfterTransition(); 145 return; 146 } 147 148 android.app.Fragment fragment = null; 149 Fragment androidXFragment = null; 150 String action = getIntent().getAction(); 151 152 getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 153 154 long sessionId = getIntent().getLongExtra(Constants.EXTRA_SESSION_ID, INVALID_SESSION_ID); 155 while (sessionId == INVALID_SESSION_ID) { 156 sessionId = new Random().nextLong(); 157 } 158 159 int autoRevokeAction = 160 APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__OPENED_FOR_AUTO_REVOKE; 161 int openFromIntentAction = 162 APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__OPENED_FROM_INTENT; 163 164 String permissionName; 165 switch (action) { 166 case Intent.ACTION_MANAGE_PERMISSIONS: 167 Bundle arguments = new Bundle(); 168 arguments.putLong(EXTRA_SESSION_ID, sessionId); 169 if (DeviceUtils.isAuto(this)) { 170 androidXFragment = AutoManageStandardPermissionsFragment.newInstance(); 171 androidXFragment.setArguments(arguments); 172 } else if (DeviceUtils.isTelevision(this)) { 173 androidXFragment = 174 com.android.permissioncontroller.permission.ui.television 175 .ManagePermissionsFragment.newInstance(); 176 } else { 177 setContentView(R.layout.nav_host_fragment); 178 Navigation.findNavController(this, R.id.nav_host_fragment).setGraph( 179 R.navigation.nav_graph, arguments); 180 return; 181 182 } 183 break; 184 185 case Intent.ACTION_REVIEW_PERMISSION_USAGE: { 186 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { 187 finishAfterTransition(); 188 return; 189 } 190 191 PermissionControllerStatsLog.write(PERMISSION_USAGE_FRAGMENT_INTERACTION, sessionId, 192 PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__OPEN); 193 if (DeviceUtils.isAuto(this)) { 194 androidXFragment = new AutoPermissionUsageFragment(); 195 } else { 196 androidXFragment = PermissionUsageV2WrapperFragment.newInstance( 197 Long.MAX_VALUE, sessionId); 198 } 199 } break; 200 201 case Intent.ACTION_REVIEW_PERMISSION_HISTORY: { 202 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { 203 finishAfterTransition(); 204 return; 205 } 206 207 String groupName = getIntent() 208 .getStringExtra(Intent.EXTRA_PERMISSION_GROUP_NAME); 209 boolean showSystem = getIntent() 210 .getBooleanExtra(EXTRA_SHOW_SYSTEM, false); 211 boolean show7Days = getIntent() 212 .getBooleanExtra(EXTRA_SHOW_7_DAYS, false); 213 if (DeviceUtils.isAuto(this)) { 214 androidXFragment = AutoPermissionUsageDetailsFragment.Companion.newInstance( 215 groupName, showSystem, sessionId); 216 } else { 217 androidXFragment = PermissionDetailsWrapperFragment 218 .newInstance(groupName, Long.MAX_VALUE, showSystem, sessionId, 219 show7Days); 220 } 221 break; 222 } 223 224 case Intent.ACTION_MANAGE_APP_PERMISSION: { 225 if (DeviceUtils.isAuto(this) || DeviceUtils.isTelevision(this) 226 || DeviceUtils.isWear(this)) { 227 Intent compatIntent = new Intent(this, AppPermissionActivity.class); 228 compatIntent.putExtras(getIntent().getExtras()); 229 startActivityForResult(compatIntent, PROXY_ACTIVITY_REQUEST_CODE); 230 return; 231 } 232 String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME); 233 234 if (packageName == null) { 235 Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PACKAGE_NAME"); 236 finishAfterTransition(); 237 return; 238 } 239 permissionName = getIntent().getStringExtra(Intent.EXTRA_PERMISSION_NAME); 240 String groupName = getIntent().getStringExtra(Intent.EXTRA_PERMISSION_GROUP_NAME); 241 242 if (permissionName == null && groupName == null) { 243 Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PERMISSION_NAME or" 244 + "EXTRA_PERMISSION_GROUP_NAME"); 245 finishAfterTransition(); 246 return; 247 } 248 249 UserHandle userHandle = getIntent().getParcelableExtra(Intent.EXTRA_USER); 250 String caller = getIntent().getStringExtra(EXTRA_CALLER_NAME); 251 252 if (groupName == null) { 253 groupName = getGroupFromPermission(permissionName); 254 } 255 256 if (groupName != null 257 && groupName.equals(Manifest.permission_group.NOTIFICATIONS)) { 258 // Redirect notification group to notification settings 259 Utils.navigateToAppNotificationSettings(this, packageName, userHandle); 260 finishAfterTransition(); 261 return; 262 } 263 264 Bundle args = AppPermissionFragment.createArgs(packageName, permissionName, 265 groupName, userHandle, caller, sessionId, null); 266 setNavGraph(args, R.id.app_permission); 267 return; 268 } 269 270 case Intent.ACTION_MANAGE_APP_PERMISSIONS: { 271 String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME); 272 if (packageName == null) { 273 Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PACKAGE_NAME"); 274 finishAfterTransition(); 275 return; 276 } 277 278 UserHandle userHandle = getIntent().getParcelableExtra(Intent.EXTRA_USER); 279 if (userHandle == null) { 280 userHandle = Process.myUserHandle(); 281 } 282 283 try { 284 int uid = getPackageManager().getApplicationInfoAsUser(packageName, 0, 285 userHandle).uid; 286 long settingsSessionId = getIntent().getLongExtra( 287 Intent.ACTION_AUTO_REVOKE_PERMISSIONS, INVALID_SESSION_ID); 288 if (settingsSessionId != INVALID_SESSION_ID) { 289 sessionId = settingsSessionId; 290 Log.i(LOG_TAG, "sessionId: " + sessionId 291 + " Reaching AppPermissionGroupsFragment for auto revoke. " 292 + "packageName: " + packageName + " uid " + uid); 293 PermissionControllerStatsLog.write( 294 APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION, sessionId, uid, 295 packageName, autoRevokeAction); 296 } else { 297 if (KotlinUtils.INSTANCE.isROrAutoRevokeEnabled(getApplication(), 298 packageName, userHandle)) { 299 Log.i(LOG_TAG, "sessionId: " + sessionId 300 + " Reaching AppPermissionGroupsFragment from intent. " 301 + "packageName " + packageName + " uid " + uid); 302 PermissionControllerStatsLog.write( 303 APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION, sessionId, 304 uid, packageName, openFromIntentAction); 305 } 306 } 307 } catch (PackageManager.NameNotFoundException e) { 308 // Do no logging 309 } 310 311 final boolean allPermissions = getIntent().getBooleanExtra( 312 EXTRA_ALL_PERMISSIONS, false); 313 314 315 if (DeviceUtils.isAuto(this)) { 316 if (allPermissions) { 317 androidXFragment = AutoAllAppPermissionsFragment.newInstance(packageName, 318 userHandle, sessionId); 319 } else { 320 androidXFragment = AutoAppPermissionsFragment.newInstance(packageName, 321 userHandle, sessionId, /* isSystemPermsScreen= */ true); 322 } 323 } else if (DeviceUtils.isWear(this)) { 324 androidXFragment = AppPermissionsFragmentWear.newInstance(packageName); 325 } else if (DeviceUtils.isTelevision(this)) { 326 androidXFragment = com.android.permissioncontroller.permission.ui.television 327 .AppPermissionsFragment.newInstance(packageName, userHandle); 328 } else { 329 Bundle args = AppPermissionGroupsFragment.createArgs(packageName, userHandle, 330 sessionId, /* isSystemPermsScreen= */ true); 331 setNavGraph(args, R.id.app_permission_groups); 332 return; 333 } 334 } break; 335 336 case Intent.ACTION_MANAGE_PERMISSION_APPS: { 337 permissionName = getIntent().getStringExtra(Intent.EXTRA_PERMISSION_NAME); 338 339 String permissionGroupName = getIntent().getStringExtra( 340 Intent.EXTRA_PERMISSION_GROUP_NAME); 341 342 if (permissionGroupName == null) { 343 try { 344 PermissionInfo permInfo = getPackageManager().getPermissionInfo( 345 permissionName, 0); 346 permissionGroupName = Utils.getGroupOfPermission(permInfo); 347 } catch (PackageManager.NameNotFoundException e) { 348 Log.i(LOG_TAG, "Permission " + permissionName + " does not exist"); 349 } 350 } 351 352 if (permissionGroupName == null) { 353 permissionGroupName = permissionName; 354 } 355 356 if (permissionName == null && permissionGroupName == null) { 357 Log.i(LOG_TAG, "Missing mandatory argument EXTRA_PERMISSION_NAME or" 358 + "EXTRA_PERMISSION_GROUP_NAME"); 359 finishAfterTransition(); 360 return; 361 } 362 363 // Redirect notification group to notification settings 364 if (permissionGroupName.equals(Manifest.permission_group.NOTIFICATIONS)) { 365 Utils.navigateToNotificationSettings(this); 366 finishAfterTransition(); 367 return; 368 } 369 370 if (DeviceUtils.isAuto(this)) { 371 androidXFragment = 372 AutoPermissionAppsFragment.newInstance(permissionGroupName, sessionId); 373 } else if (DeviceUtils.isTelevision(this)) { 374 androidXFragment = com.android.permissioncontroller.permission.ui.television 375 .PermissionAppsFragment.newInstance(permissionGroupName); 376 } else { 377 Bundle args = PermissionAppsFragment.createArgs(permissionGroupName, sessionId); 378 setNavGraph(args, R.id.permission_apps); 379 return; 380 } 381 } break; 382 383 case Intent.ACTION_MANAGE_UNUSED_APPS : 384 // fall through 385 case ACTION_MANAGE_AUTO_REVOKE: { 386 Log.i(LOG_TAG, "sessionId " + sessionId + " starting auto revoke fragment" 387 + " from notification"); 388 PermissionControllerStatsLog.write(AUTO_REVOKE_NOTIFICATION_CLICKED, sessionId); 389 390 if (DeviceUtils.isAuto(this)) { 391 androidXFragment = AutoUnusedAppsFragment.newInstance(); 392 androidXFragment.setArguments(UnusedAppsFragment.createArgs(sessionId)); 393 } else if (DeviceUtils.isTelevision(this)) { 394 androidXFragment = TvUnusedAppsFragment.newInstance(); 395 androidXFragment.setArguments(UnusedAppsFragment.createArgs(sessionId)); 396 } else if (DeviceUtils.isWear(this)) { 397 androidXFragment = HandheldUnusedAppsWrapperFragment.newInstance(); 398 androidXFragment.setArguments(UnusedAppsFragment.createArgs(sessionId)); 399 } else { 400 setNavGraph(UnusedAppsFragment.createArgs(sessionId), R.id.auto_revoke); 401 return; 402 } 403 } break; 404 case PermissionManager.ACTION_REVIEW_PERMISSION_DECISIONS: { 405 406 UserHandle userHandle = getIntent().getParcelableExtra(Intent.EXTRA_USER); 407 if (userHandle == null) { 408 userHandle = Process.myUserHandle(); 409 } 410 if (DeviceUtils.isAuto(this)) { 411 String source = getIntent().getStringExtra( 412 AutoReviewPermissionDecisionsFragment.EXTRA_SOURCE); 413 androidXFragment = AutoReviewPermissionDecisionsFragment.Companion 414 .newInstance(sessionId, userHandle, source); 415 } else { 416 Log.e(LOG_TAG, "ACTION_REVIEW_PERMISSION_DECISIONS is not " 417 + "supported on this device type"); 418 finishAfterTransition(); 419 return; 420 } 421 } break; 422 423 default: { 424 Log.w(LOG_TAG, "Unrecognized action " + action); 425 finishAfterTransition(); 426 return; 427 } 428 } 429 430 if (fragment != null) { 431 getFragmentManager().beginTransaction().replace(android.R.id.content, fragment) 432 .commit(); 433 } else if (androidXFragment != null) { 434 getSupportFragmentManager().beginTransaction().replace(android.R.id.content, 435 androidXFragment).commit(); 436 } 437 } 438 getGroupFromPermission(String permissionName)439 private String getGroupFromPermission(String permissionName) { 440 try { 441 PermissionInfo permInfo = getPackageManager().getPermissionInfo( 442 permissionName, 0); 443 return Utils.getGroupOfPermission(permInfo); 444 } catch (PackageManager.NameNotFoundException e) { 445 Log.i(LOG_TAG, "Permission " + permissionName + " does not exist"); 446 } 447 return null; 448 } 449 setNavGraph(Bundle args, int startDestination)450 private void setNavGraph(Bundle args, int startDestination) { 451 setContentView(R.layout.nav_host_fragment); 452 NavHostFragment navHost = (NavHostFragment) getSupportFragmentManager() 453 .findFragmentById(R.id.nav_host_fragment); 454 NavInflater inflater = navHost.getNavController().getNavInflater(); 455 NavGraph graph = inflater.inflate(R.navigation.nav_graph); 456 graph.setStartDestination(startDestination); 457 navHost.getNavController().setGraph(graph, args); 458 } 459 460 @Override getActionBar()461 public ActionBar getActionBar() { 462 ActionBar ab = super.getActionBar(); 463 if (ab != null) { 464 ab.setHomeActionContentDescription(R.string.back); 465 } 466 return ab; 467 } 468 469 @Override onOptionsItemSelected(MenuItem item)470 public boolean onOptionsItemSelected(MenuItem item) { 471 // in automotive mode, there's no system wide back button, so need to add that 472 if (DeviceUtils.isAuto(this)) { 473 switch (item.getItemId()) { 474 case android.R.id.home: 475 onBackPressed(); 476 finishAfterTransition(); 477 return true; 478 default: 479 return super.onOptionsItemSelected(item); 480 } 481 } 482 return super.onOptionsItemSelected(item); 483 } 484 485 @Override onActivityResult(int requestCode, int resultCode, Intent data)486 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 487 super.onActivityResult(requestCode, resultCode, data); 488 if (requestCode == PROXY_ACTIVITY_REQUEST_CODE) { 489 setResult(resultCode, data); 490 finishAfterTransition(); 491 } 492 } 493 494 } 495