1 /* 2 * Copyright (C) 2018 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.handheld; 18 19 import static android.Manifest.permission_group.STORAGE; 20 21 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID; 22 import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID; 23 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW; 24 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS; 25 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND; 26 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ASK_EVERY_TIME; 27 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY; 28 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY_FOREGROUND; 29 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__GRANT_FINE_LOCATION; 30 import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__REVOKE_FINE_LOCATION; 31 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED; 32 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN; 33 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS; 34 import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY; 35 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME; 36 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED; 37 import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT; 38 import static com.android.permissioncontroller.permission.ui.handheld.UtilsKt.pressBack; 39 40 import android.app.ActionBar; 41 import android.app.Activity; 42 import android.app.AlertDialog; 43 import android.app.Dialog; 44 import android.content.Context; 45 import android.content.DialogInterface; 46 import android.content.Intent; 47 import android.graphics.drawable.Drawable; 48 import android.os.Bundle; 49 import android.os.Handler; 50 import android.os.Looper; 51 import android.os.UserHandle; 52 import android.text.BidiFormatter; 53 import android.util.Log; 54 import android.view.LayoutInflater; 55 import android.view.MenuItem; 56 import android.view.View; 57 import android.view.ViewGroup; 58 import android.widget.CompoundButton; 59 import android.widget.ImageView; 60 import android.widget.RadioButton; 61 import android.widget.Switch; 62 import android.widget.TextView; 63 import android.widget.Toast; 64 65 import androidx.annotation.NonNull; 66 import androidx.annotation.Nullable; 67 import androidx.annotation.StringRes; 68 import androidx.core.widget.NestedScrollView; 69 import androidx.fragment.app.DialogFragment; 70 import androidx.lifecycle.ViewModelProvider; 71 72 import com.android.permissioncontroller.R; 73 import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData.FullStoragePackageState; 74 import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler; 75 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel; 76 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonState; 77 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType; 78 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ChangeRequest; 79 import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModelFactory; 80 import com.android.permissioncontroller.permission.utils.KotlinUtils; 81 import com.android.settingslib.RestrictedLockUtils; 82 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 83 import com.android.settingslib.widget.ActionBarShadowController; 84 85 import java.util.Map; 86 import java.util.Objects; 87 88 import kotlin.Pair; 89 90 /** 91 * Show and manage a single permission group for an app. 92 * 93 * <p>Allows the user to control whether the app is granted the permission. 94 */ 95 public class AppPermissionFragment extends SettingsWithLargeHeader 96 implements AppPermissionViewModel.ConfirmDialogShowingFragment { 97 private static final String LOG_TAG = "AppPermissionFragment"; 98 private static final long POST_DELAY_MS = 20; 99 100 static final String GRANT_CATEGORY = "grant_category"; 101 102 private @NonNull AppPermissionViewModel mViewModel; 103 private @NonNull RadioButton mAllowButton; 104 private @NonNull RadioButton mAllowAlwaysButton; 105 private @NonNull RadioButton mAllowForegroundButton; 106 private @NonNull RadioButton mAskOneTimeButton; 107 private @NonNull RadioButton mAskButton; 108 private @NonNull RadioButton mDenyButton; 109 private @NonNull RadioButton mDenyForegroundButton; 110 private @NonNull View mLocationAccuracy; 111 private @NonNull Switch mLocationAccuracySwitch; 112 private @NonNull View mDivider; 113 private @NonNull ViewGroup mWidgetFrame; 114 private @NonNull TextView mPermissionDetails; 115 private @NonNull NestedScrollView mNestedScrollView; 116 private @NonNull String mPackageName; 117 private @NonNull String mPermGroupName; 118 private @NonNull UserHandle mUser; 119 private boolean mIsStorageGroup; 120 private boolean mIsInitialLoad; 121 private long mSessionId; 122 123 private @NonNull String mPackageLabel; 124 private @NonNull String mPermGroupLabel; 125 private Drawable mPackageIcon; 126 127 /** 128 * Create a bundle with the arguments needed by this fragment 129 * 130 * @param packageName The name of the package 131 * @param permName The name of the permission whose group this fragment is for (optional) 132 * @param groupName The name of the permission group (required if permName not specified) 133 * @param userHandle The user of the app permission group 134 * @param caller The name of the fragment we called from 135 * @param sessionId The current session ID 136 * @param grantCategory The grant status of this app permission group. Used to initially set 137 * the button state 138 * @return A bundle with all of the args placed 139 */ createArgs(@onNull String packageName, @Nullable String permName, @Nullable String groupName, @NonNull UserHandle userHandle, @Nullable String caller, long sessionId, @Nullable String grantCategory)140 public static Bundle createArgs(@NonNull String packageName, 141 @Nullable String permName, @Nullable String groupName, 142 @NonNull UserHandle userHandle, @Nullable String caller, long sessionId, @Nullable 143 String grantCategory) { 144 Bundle arguments = new Bundle(); 145 arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName); 146 if (groupName == null) { 147 arguments.putString(Intent.EXTRA_PERMISSION_NAME, permName); 148 } else { 149 arguments.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName); 150 } 151 arguments.putParcelable(Intent.EXTRA_USER, userHandle); 152 arguments.putString(EXTRA_CALLER_NAME, caller); 153 arguments.putLong(EXTRA_SESSION_ID, sessionId); 154 arguments.putString(GRANT_CATEGORY, grantCategory); 155 return arguments; 156 } 157 158 @Override onCreate(Bundle savedInstanceState)159 public void onCreate(Bundle savedInstanceState) { 160 super.onCreate(savedInstanceState); 161 162 setHasOptionsMenu(true); 163 ActionBar ab = getActivity().getActionBar(); 164 if (ab != null) { 165 ab.setDisplayHomeAsUpEnabled(true); 166 } 167 168 mPackageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME); 169 mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME); 170 if (mPermGroupName == null) { 171 mPermGroupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME); 172 } 173 mIsStorageGroup = Objects.equals(mPermGroupName, STORAGE); 174 mUser = getArguments().getParcelable(Intent.EXTRA_USER); 175 mPackageLabel = BidiFormatter.getInstance().unicodeWrap( 176 KotlinUtils.INSTANCE.getPackageLabel(getActivity().getApplication(), mPackageName, 177 mUser)); 178 mPermGroupLabel = KotlinUtils.INSTANCE.getPermGroupLabel(getContext(), 179 mPermGroupName).toString(); 180 mPackageIcon = KotlinUtils.INSTANCE.getBadgedPackageIcon(getActivity().getApplication(), 181 mPackageName, mUser); 182 mSessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID); 183 184 AppPermissionViewModelFactory factory = new AppPermissionViewModelFactory( 185 getActivity().getApplication(), mPackageName, mPermGroupName, mUser, mSessionId); 186 mViewModel = new ViewModelProvider(this, factory).get(AppPermissionViewModel.class); 187 Handler delayHandler = new Handler(Looper.getMainLooper()); 188 mViewModel.getButtonStateLiveData().observe(this, buttonState -> { 189 if (mIsInitialLoad) { 190 setRadioButtonsState(buttonState); 191 } else { 192 delayHandler.removeCallbacksAndMessages(null); 193 delayHandler.postDelayed(() -> setRadioButtonsState(buttonState), POST_DELAY_MS); 194 } 195 }); 196 mViewModel.getDetailResIdLiveData().observe(this, this::setDetail); 197 mViewModel.getShowAdminSupportLiveData().observe(this, this::setAdminSupportDetail); 198 if (mIsStorageGroup) { 199 mViewModel.getFullStorageStateLiveData().observe(this, this::setSpecialStorageState); 200 } 201 } 202 203 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)204 public View onCreateView(LayoutInflater inflater, ViewGroup container, 205 Bundle savedInstanceState) { 206 Context context = getContext(); 207 ViewGroup root = (ViewGroup) inflater.inflate(R.layout.app_permission, container, false); 208 209 mIsInitialLoad = true; 210 211 setHeader(mPackageIcon, mPackageLabel, null, null, false); 212 updateHeader(root.requireViewById(R.id.large_header)); 213 214 ((TextView) root.requireViewById(R.id.permission_message)).setText( 215 context.getString(R.string.app_permission_header, mPermGroupLabel)); 216 217 String caller = getArguments().getString(EXTRA_CALLER_NAME); 218 219 TextView footer1Link = root.requireViewById(R.id.footer_link_1); 220 footer1Link.setText(context.getString(R.string.app_permission_footer_app_permissions_link, 221 mPackageLabel)); 222 setBottomLinkState(footer1Link, caller, Intent.ACTION_MANAGE_APP_PERMISSIONS); 223 224 TextView footer2Link = root.requireViewById(R.id.footer_link_2); 225 footer2Link.setText(context.getString(R.string.app_permission_footer_permission_apps_link)); 226 setBottomLinkState(footer2Link, caller, Intent.ACTION_MANAGE_PERMISSION_APPS); 227 228 mAllowButton = root.requireViewById(R.id.allow_radio_button); 229 mAllowAlwaysButton = root.requireViewById(R.id.allow_always_radio_button); 230 mAllowForegroundButton = root.requireViewById(R.id.allow_foreground_only_radio_button); 231 mAskOneTimeButton = root.requireViewById(R.id.ask_one_time_radio_button); 232 mAskButton = root.requireViewById(R.id.ask_radio_button); 233 mDenyButton = root.requireViewById(R.id.deny_radio_button); 234 mDenyForegroundButton = root.requireViewById(R.id.deny_foreground_radio_button); 235 mDivider = root.requireViewById(R.id.two_target_divider); 236 mWidgetFrame = root.requireViewById(R.id.widget_frame); 237 mPermissionDetails = root.requireViewById(R.id.permission_details); 238 mLocationAccuracy = root.requireViewById(R.id.location_accuracy); 239 mLocationAccuracySwitch = root.requireViewById(R.id.location_accuracy_switch); 240 241 mNestedScrollView = root.requireViewById(R.id.nested_scroll_view); 242 243 if (mViewModel.getButtonStateLiveData().getValue() != null) { 244 setRadioButtonsState(mViewModel.getButtonStateLiveData().getValue()); 245 } else { 246 mAllowButton.setVisibility(View.GONE); 247 mAllowAlwaysButton.setVisibility(View.GONE); 248 mAllowForegroundButton.setVisibility(View.GONE); 249 mAskOneTimeButton.setVisibility(View.GONE); 250 mAskButton.setVisibility(View.GONE); 251 mDenyButton.setVisibility(View.GONE); 252 mDenyForegroundButton.setVisibility(View.GONE); 253 mLocationAccuracy.setVisibility(View.GONE); 254 } 255 256 if (mViewModel.getFullStorageStateLiveData().isInitialized() && mIsStorageGroup) { 257 setSpecialStorageState(mViewModel.getFullStorageStateLiveData().getValue(), root); 258 } else { 259 TextView storageFooter = root.requireViewById(R.id.footer_storage_special_app_access); 260 storageFooter.setVisibility(View.GONE); 261 } 262 263 getActivity().setTitle( 264 getPreferenceManager().getContext().getString(R.string.app_permission_title, 265 mPermGroupLabel)); 266 267 return root; 268 } 269 setBottomLinkState(TextView view, String caller, String action)270 private void setBottomLinkState(TextView view, String caller, String action) { 271 if ((Objects.equals(caller, AppPermissionGroupsFragment.class.getName()) 272 && action.equals(Intent.ACTION_MANAGE_APP_PERMISSIONS)) 273 || (Objects.equals(caller, PermissionAppsFragment.class.getName()) 274 && action.equals(Intent.ACTION_MANAGE_PERMISSION_APPS))) { 275 view.setVisibility(View.GONE); 276 } else { 277 view.setOnClickListener((v) -> { 278 Bundle args; 279 if (action.equals(Intent.ACTION_MANAGE_APP_PERMISSIONS)) { 280 args = AppPermissionGroupsFragment.createArgs(mPackageName, mUser, 281 mSessionId, true); 282 } else { 283 args = PermissionAppsFragment.createArgs(mPermGroupName, mSessionId); 284 } 285 mViewModel.showBottomLinkPage(this, action, args); 286 }); 287 } 288 } 289 setSpecialStorageState(FullStoragePackageState storageState)290 private void setSpecialStorageState(FullStoragePackageState storageState) { 291 setSpecialStorageState(storageState, getView()); 292 } 293 294 @Override onStart()295 public void onStart() { 296 super.onStart(); 297 298 ActionBar ab = getActivity().getActionBar(); 299 if (ab != null) { 300 ab.setElevation(0); 301 } 302 303 ActionBarShadowController.attachToView(getActivity(), getLifecycle(), mNestedScrollView); 304 } 305 306 @Override onOptionsItemSelected(MenuItem item)307 public boolean onOptionsItemSelected(MenuItem item) { 308 if (item.getItemId() == android.R.id.home) { 309 pressBack(this); 310 return true; 311 } 312 return super.onOptionsItemSelected(item); 313 } 314 setRadioButtonsState(Map<ButtonType, ButtonState> states)315 private void setRadioButtonsState(Map<ButtonType, ButtonState> states) { 316 if (states == null && mViewModel.getButtonStateLiveData().isInitialized()) { 317 pressBack(this); 318 Log.w(LOG_TAG, "invalid package " + mPackageName + " or perm group " 319 + mPermGroupName); 320 Toast.makeText( 321 getActivity(), R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show(); 322 return; 323 } else if (states == null) { 324 return; 325 } 326 327 mAllowButton.setOnClickListener((v) -> { 328 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_FOREGROUND, 329 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW); 330 setResult(GRANTED_ALWAYS); 331 }); 332 mAllowAlwaysButton.setOnClickListener((v) -> { 333 if (mIsStorageGroup) { 334 showConfirmDialog(ChangeRequest.GRANT_All_FILE_ACCESS, 335 R.string.special_file_access_dialog, -1, false); 336 } else { 337 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_BOTH, 338 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS); 339 } 340 setResult(GRANTED_ALWAYS); 341 }); 342 mAllowForegroundButton.setOnClickListener((v) -> { 343 if (mIsStorageGroup) { 344 mViewModel.setAllFilesAccess(false); 345 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_BOTH, 346 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW); 347 setResult(GRANTED_ALWAYS); 348 } else { 349 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_FOREGROUND_ONLY, 350 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND); 351 setResult(GRANTED_FOREGROUND_ONLY); 352 } 353 }); 354 // mAskOneTimeButton only shows if checked hence should do nothing 355 mAskButton.setOnClickListener((v) -> { 356 mViewModel.requestChange(true, this, this, ChangeRequest.REVOKE_BOTH, 357 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ASK_EVERY_TIME); 358 setResult(DENIED); 359 }); 360 mDenyButton.setOnClickListener((v) -> { 361 362 if (mViewModel.getFullStorageStateLiveData().getValue() != null 363 && !mViewModel.getFullStorageStateLiveData().getValue().isLegacy()) { 364 mViewModel.setAllFilesAccess(false); 365 } 366 mViewModel.requestChange(false, this, this, ChangeRequest.REVOKE_BOTH, 367 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY); 368 setResult(DENIED_DO_NOT_ASK_AGAIN); 369 }); 370 mDenyForegroundButton.setOnClickListener((v) -> { 371 mViewModel.requestChange(false, this, this, ChangeRequest.REVOKE_FOREGROUND, 372 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY_FOREGROUND); 373 setResult(DENIED_DO_NOT_ASK_AGAIN); 374 }); 375 // Set long variable names to new variables to bypass linter errors. 376 int grantFineLocation = 377 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__GRANT_FINE_LOCATION; 378 int revokeFineLocation = 379 APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__REVOKE_FINE_LOCATION; 380 mLocationAccuracy.setOnClickListener((v) -> { 381 mLocationAccuracySwitch.performClick(); 382 if (mLocationAccuracySwitch.isChecked()) { 383 mViewModel.requestChange(false, this, this, ChangeRequest.GRANT_FINE_LOCATION, 384 grantFineLocation); 385 } else { 386 mViewModel.requestChange(false, this, this, ChangeRequest.REVOKE_FINE_LOCATION, 387 revokeFineLocation); 388 } 389 }); 390 391 setButtonState(mAllowButton, states.get(ButtonType.ALLOW)); 392 setButtonState(mAllowAlwaysButton, states.get(ButtonType.ALLOW_ALWAYS)); 393 setButtonState(mAllowForegroundButton, states.get(ButtonType.ALLOW_FOREGROUND)); 394 setButtonState(mAskOneTimeButton, states.get(ButtonType.ASK_ONCE)); 395 setButtonState(mAskButton, states.get(ButtonType.ASK)); 396 setButtonState(mDenyButton, states.get(ButtonType.DENY)); 397 setButtonState(mDenyForegroundButton, states.get(ButtonType.DENY_FOREGROUND)); 398 399 ButtonState locationAccuracyState = states.get(ButtonType.LOCATION_ACCURACY); 400 if (!locationAccuracyState.isShown()) { 401 mLocationAccuracy.setVisibility(View.GONE); 402 } else { 403 mLocationAccuracy.setVisibility(View.VISIBLE); 404 } 405 mLocationAccuracySwitch.setChecked(locationAccuracyState.isChecked()); 406 if (!locationAccuracyState.isEnabled()) { 407 mLocationAccuracy.setEnabled(false); 408 mLocationAccuracySwitch.setEnabled(false); 409 } 410 411 mIsInitialLoad = false; 412 413 if (mViewModel.getFullStorageStateLiveData().isInitialized()) { 414 setSpecialStorageState(mViewModel.getFullStorageStateLiveData().getValue()); 415 } 416 } 417 setButtonState(CompoundButton button, AppPermissionViewModel.ButtonState state)418 private void setButtonState(CompoundButton button, AppPermissionViewModel.ButtonState state) { 419 int visible = state.isShown() ? View.VISIBLE : View.GONE; 420 button.setVisibility(visible); 421 if (state.isShown()) { 422 button.setChecked(state.isChecked()); 423 button.setEnabled(state.isEnabled()); 424 } 425 if (mIsInitialLoad) { 426 button.jumpDrawablesToCurrentState(); 427 } 428 } 429 setSpecialStorageState(FullStoragePackageState storageState, View v)430 private void setSpecialStorageState(FullStoragePackageState storageState, View v) { 431 if (v == null) { 432 return; 433 } 434 435 TextView textView = v.requireViewById(R.id.footer_storage_special_app_access); 436 if (mAllowButton == null || !mIsStorageGroup) { 437 textView.setVisibility(View.GONE); 438 return; 439 } 440 441 mAllowAlwaysButton.setText(R.string.app_permission_button_allow_all_files); 442 mAllowForegroundButton.setText(R.string.app_permission_button_allow_media_only); 443 444 if (storageState == null) { 445 textView.setVisibility(View.GONE); 446 return; 447 } 448 449 if (storageState.isLegacy()) { 450 mAllowButton.setText(R.string.app_permission_button_allow_all_files); 451 textView.setVisibility(View.GONE); 452 return; 453 } 454 455 textView.setText(R.string.app_permission_footer_special_file_access); 456 textView.setVisibility(View.VISIBLE); 457 } 458 setResult(@rantPermissionsViewHandler.Result int result)459 private void setResult(@GrantPermissionsViewHandler.Result int result) { 460 if (!mPackageName.equals( 461 getActivity().getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME))) { 462 return; 463 } 464 Intent intent = new Intent() 465 .putExtra(EXTRA_RESULT_PERMISSION_INTERACTED, mPermGroupName) 466 .putExtra(EXTRA_RESULT_PERMISSION_RESULT, result); 467 getActivity().setResult(Activity.RESULT_OK, intent); 468 } 469 setDetail(Pair<Integer, Integer> detailResIds)470 private void setDetail(Pair<Integer, Integer> detailResIds) { 471 if (detailResIds == null) { 472 mWidgetFrame.setVisibility(View.GONE); 473 mDivider.setVisibility(View.GONE); 474 return; 475 } 476 mWidgetFrame.setVisibility(View.VISIBLE); 477 if (detailResIds.getSecond() != null) { 478 // If the permissions are individually controlled, also show a link to the page that 479 // lets you control them. 480 mDivider.setVisibility(View.VISIBLE); 481 showRightIcon(R.drawable.ic_settings); 482 Bundle args = AllAppPermissionsFragment.createArgs(mPackageName, mPermGroupName, mUser); 483 mWidgetFrame.setOnClickListener(v -> mViewModel.showAllPermissions(this, args)); 484 mPermissionDetails.setText(getPreferenceManager().getContext().getString( 485 detailResIds.getFirst(), detailResIds.getSecond())); 486 } else { 487 mPermissionDetails.setText(getPreferenceManager().getContext().getString( 488 detailResIds.getFirst())); 489 } 490 mPermissionDetails.setVisibility(View.VISIBLE); 491 492 } 493 setAdminSupportDetail(EnforcedAdmin admin)494 private void setAdminSupportDetail(EnforcedAdmin admin) { 495 if (admin != null) { 496 showRightIcon(R.drawable.ic_info); 497 mWidgetFrame.setOnClickListener(v -> 498 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), admin) 499 ); 500 } else { 501 mWidgetFrame.removeAllViews(); 502 } 503 } 504 505 /** 506 * Show the given icon on the right of the first radio button. 507 * 508 * @param iconId the resourceId of the drawable to use. 509 */ showRightIcon(int iconId)510 private void showRightIcon(int iconId) { 511 mWidgetFrame.removeAllViews(); 512 ImageView imageView = new ImageView(getPreferenceManager().getContext()); 513 imageView.setImageResource(iconId); 514 mWidgetFrame.addView(imageView); 515 mWidgetFrame.setVisibility(View.VISIBLE); 516 } 517 518 /** 519 * Show a dialog that warns the users that they are about to revoke permissions that were 520 * granted by default, or that they are about to grant full file access to an app. 521 * 522 * 523 * The order of operation to revoke a permission granted by default is: 524 * 1. `showConfirmDialog` 525 * 1. [ConfirmDialog.onCreateDialog] 526 * 1. [AppPermissionViewModel.onDenyAnyWay] or [AppPermissionViewModel.onConfirmFileAccess] 527 * TODO: Remove once data can be passed between dialogs and fragments with nav component 528 * 529 * @param changeRequest Whether background or foreground should be changed 530 * @param messageId The Id of the string message to show 531 * @param buttonPressed Button which was pressed to initiate the dialog, one of 532 * AppPermissionFragmentActionReported.button_pressed constants 533 * @param oneTime Whether the one-time (ask) button was clicked rather than the deny 534 * button 535 */ 536 @Override showConfirmDialog(ChangeRequest changeRequest, @StringRes int messageId, int buttonPressed, boolean oneTime)537 public void showConfirmDialog(ChangeRequest changeRequest, @StringRes int messageId, 538 int buttonPressed, boolean oneTime) { 539 Bundle args = getArguments().deepCopy(); 540 args.putInt(ConfirmDialog.MSG, messageId); 541 args.putSerializable(ConfirmDialog.CHANGE_REQUEST, changeRequest); 542 args.putInt(ConfirmDialog.BUTTON, buttonPressed); 543 args.putBoolean(ConfirmDialog.ONE_TIME, oneTime); 544 ConfirmDialog defaultDenyDialog = new ConfirmDialog(); 545 defaultDenyDialog.setCancelable(true); 546 defaultDenyDialog.setArguments(args); 547 defaultDenyDialog.show(getChildFragmentManager().beginTransaction(), 548 ConfirmDialog.class.getName()); 549 } 550 551 /** 552 * A dialog warning the user that they are about to deny a permission that was granted by 553 * default, or that they are denying a permission on a Pre-M app 554 * 555 * @see AppPermissionViewModel.ConfirmDialogShowingFragment#showConfirmDialog(ChangeRequest, 556 * int, int, boolean) 557 * @see #showConfirmDialog(ChangeRequest, int, int) 558 */ 559 public static class ConfirmDialog extends DialogFragment { 560 static final String MSG = ConfirmDialog.class.getName() + ".arg.msg"; 561 static final String CHANGE_REQUEST = ConfirmDialog.class.getName() 562 + ".arg.changeRequest"; 563 private static final String KEY = ConfirmDialog.class.getName() + ".arg.key"; 564 private static final String BUTTON = ConfirmDialog.class.getName() + ".arg.button"; 565 private static final String ONE_TIME = ConfirmDialog.class.getName() + ".arg.onetime"; 566 private static int sCode = APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW; 567 @Override onCreateDialog(Bundle savedInstanceState)568 public Dialog onCreateDialog(Bundle savedInstanceState) { 569 AppPermissionFragment fragment = (AppPermissionFragment) getParentFragment(); 570 boolean isGrantFileAccess = getArguments().getSerializable(CHANGE_REQUEST) 571 == ChangeRequest.GRANT_All_FILE_ACCESS; 572 int positiveButtonStringResId = R.string.grant_dialog_button_deny_anyway; 573 if (isGrantFileAccess) { 574 positiveButtonStringResId = R.string.grant_dialog_button_allow; 575 } 576 AlertDialog.Builder b = new AlertDialog.Builder(getContext()) 577 .setMessage(getArguments().getInt(MSG)) 578 .setNegativeButton(R.string.cancel, 579 (DialogInterface dialog, int which) -> dialog.cancel()) 580 .setPositiveButton(positiveButtonStringResId, 581 (DialogInterface dialog, int which) -> { 582 if (isGrantFileAccess) { 583 fragment.mViewModel.setAllFilesAccess(true); 584 fragment.mViewModel.requestChange(false, fragment, 585 fragment, ChangeRequest.GRANT_BOTH, sCode); 586 } else { 587 fragment.mViewModel.onDenyAnyWay((ChangeRequest) 588 getArguments().getSerializable(CHANGE_REQUEST), 589 getArguments().getInt(BUTTON), 590 getArguments().getBoolean(ONE_TIME)); 591 } 592 }); 593 Dialog d = b.create(); 594 d.setCanceledOnTouchOutside(true); 595 return d; 596 } 597 598 @Override onCancel(DialogInterface dialog)599 public void onCancel(DialogInterface dialog) { 600 AppPermissionFragment fragment = (AppPermissionFragment) getParentFragment(); 601 fragment.setRadioButtonsState(fragment.mViewModel.getButtonStateLiveData().getValue()); 602 } 603 } 604 } 605