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.car.settings.accounts; 18 19 import static android.os.UserManager.DISALLOW_MODIFY_ACCOUNTS; 20 21 import static com.android.car.settings.enterprise.ActionDisabledByAdminDialogFragment.DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG; 22 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByDpm; 23 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByUm; 24 25 import android.accounts.AccountManager; 26 import android.accounts.AccountManagerCallback; 27 import android.accounts.AuthenticatorException; 28 import android.accounts.OperationCanceledException; 29 import android.car.drivingstate.CarUxRestrictions; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.os.Bundle; 33 34 import androidx.annotation.CallSuper; 35 import androidx.annotation.VisibleForTesting; 36 37 import com.android.car.settings.R; 38 import com.android.car.settings.common.ActivityResultCallback; 39 import com.android.car.settings.common.ConfirmationDialogFragment; 40 import com.android.car.settings.common.ErrorDialog; 41 import com.android.car.settings.common.FragmentController; 42 import com.android.car.settings.common.Logger; 43 import com.android.car.settings.enterprise.EnterpriseUtils; 44 import com.android.car.settings.profiles.ProfileHelper; 45 46 import java.io.IOException; 47 48 /** 49 * Controller for the preference that shows the details of an account. It also handles a secondary 50 * button for account removal. 51 */ 52 public class AccountDetailsPreferenceController extends AccountDetailsBasePreferenceController 53 implements ActivityResultCallback { 54 private static final Logger LOG = new Logger(AccountDetailsPreferenceController.class); 55 private static final int REMOVE_ACCOUNT_REQUEST = 101; 56 57 private AccountManagerCallback<Bundle> mCallback = 58 future -> { 59 // If already out of this screen, don't proceed. 60 if (!isStarted()) { 61 return; 62 } 63 64 boolean done = true; 65 boolean success = false; 66 try { 67 Bundle result = future.getResult(); 68 Intent removeIntent = result.getParcelable(AccountManager.KEY_INTENT); 69 if (removeIntent != null) { 70 done = false; 71 getFragmentController().startActivityForResult(new Intent(removeIntent), 72 REMOVE_ACCOUNT_REQUEST, this); 73 } else { 74 success = future.getResult().getBoolean(AccountManager.KEY_BOOLEAN_RESULT); 75 } 76 } catch (OperationCanceledException | IOException | AuthenticatorException e) { 77 LOG.v("removeAccount error: " + e); 78 } 79 if (done) { 80 if (!success) { 81 showErrorDialog(); 82 } else { 83 getFragmentController().goBack(); 84 } 85 } 86 }; 87 88 private ConfirmationDialogFragment.ConfirmListener mConfirmListener = arguments -> { 89 AccountManager.get(getContext()).removeAccountAsUser(getAccount(), /* activity= */ null, 90 mCallback, null, getUserHandle()); 91 ConfirmationDialogFragment dialog = 92 (ConfirmationDialogFragment) getFragmentController().findDialogByTag( 93 ConfirmationDialogFragment.TAG); 94 if (dialog != null) { 95 dialog.dismiss(); 96 } 97 }; 98 AccountDetailsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)99 public AccountDetailsPreferenceController(Context context, String preferenceKey, 100 FragmentController fragmentController, CarUxRestrictions uxRestrictions) { 101 super(context, preferenceKey, fragmentController, uxRestrictions); 102 } 103 104 @Override 105 @CallSuper onCreateInternal()106 protected void onCreateInternal() { 107 super.onCreateInternal(); 108 ConfirmationDialogFragment dialog = 109 (ConfirmationDialogFragment) getFragmentController().findDialogByTag( 110 ConfirmationDialogFragment.TAG); 111 ConfirmationDialogFragment.resetListeners( 112 dialog, 113 mConfirmListener, 114 /* rejectListener= */ null, 115 /* neutralListener= */ null); 116 117 getPreference().setSecondaryActionVisible( 118 getSecondaryActionAvailabilityStatus() == AVAILABLE 119 || getSecondaryActionAvailabilityStatus() == AVAILABLE_FOR_VIEWING); 120 getPreference().setSecondaryActionEnabled( 121 getSecondaryActionAvailabilityStatus() != DISABLED_FOR_PROFILE); 122 123 getPreference().setOnSecondaryActionClickListener(this::onRemoveAccountClicked); 124 } 125 126 @Override processActivityResult(int requestCode, int resultCode, Intent data)127 public void processActivityResult(int requestCode, int resultCode, Intent data) { 128 if (!isStarted()) { 129 return; 130 } 131 if (requestCode == REMOVE_ACCOUNT_REQUEST) { 132 // Activity result code may not adequately reflect the account removal status, so 133 // KEY_BOOLEAN_RESULT is used here instead. If the intent does not have this value 134 // included, a no-op will be performed and the account update listener in 135 // {@link AccountDetailsFragment} will still handle the back navigation upon removal. 136 if (data != null && data.hasExtra(AccountManager.KEY_BOOLEAN_RESULT)) { 137 boolean success = data.getBooleanExtra(AccountManager.KEY_BOOLEAN_RESULT, false); 138 if (success) { 139 getFragmentController().goBack(); 140 } else { 141 showErrorDialog(); 142 } 143 } 144 } 145 } 146 getSecondaryActionAvailabilityStatus()147 private int getSecondaryActionAvailabilityStatus() { 148 ProfileHelper profileHelper = getProfileHelper(); 149 if (profileHelper.canCurrentProcessModifyAccounts()) { 150 return AVAILABLE; 151 } 152 if (profileHelper.isDemoOrGuest() 153 || hasUserRestrictionByUm(getContext(), DISALLOW_MODIFY_ACCOUNTS)) { 154 return DISABLED_FOR_PROFILE; 155 } 156 return AVAILABLE_FOR_VIEWING; 157 } 158 onRemoveAccountClicked()159 private void onRemoveAccountClicked() { 160 if (hasUserRestrictionByDpm(getContext(), DISALLOW_MODIFY_ACCOUNTS)) { 161 showActionDisabledByAdminDialog(); 162 } 163 ConfirmationDialogFragment dialog = 164 new ConfirmationDialogFragment.Builder(getContext()) 165 .setTitle(R.string.really_remove_account_title) 166 .setMessage(R.string.really_remove_account_message) 167 .setNegativeButton(android.R.string.cancel, null) 168 .setPositiveButton(R.string.remove_account_title, mConfirmListener) 169 .build(); 170 171 getFragmentController().showDialog(dialog, ConfirmationDialogFragment.TAG); 172 } 173 showErrorDialog()174 private void showErrorDialog() { 175 getFragmentController().showDialog( 176 ErrorDialog.newInstance(R.string.remove_account_error_title), /* tag= */ null); 177 } 178 showActionDisabledByAdminDialog()179 private void showActionDisabledByAdminDialog() { 180 getFragmentController().showDialog( 181 EnterpriseUtils.getActionDisabledByAdminDialog(getContext(), 182 DISALLOW_MODIFY_ACCOUNTS), 183 DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG); 184 } 185 186 @VisibleForTesting getProfileHelper()187 ProfileHelper getProfileHelper() { 188 return ProfileHelper.getInstance(getContext()); 189 } 190 } 191