1 /*
2  * Copyright (C) 2017 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 package com.android.settings.accounts;
17 
18 import static android.app.admin.DevicePolicyResources.Strings.Settings.REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION;
19 
20 import android.accounts.Account;
21 import android.accounts.AccountManager;
22 import android.accounts.AuthenticatorException;
23 import android.accounts.OperationCanceledException;
24 import android.app.Activity;
25 import android.app.Dialog;
26 import android.app.admin.DevicePolicyManager;
27 import android.app.settings.SettingsEnums;
28 import android.content.Context;
29 import android.content.DialogInterface;
30 import android.content.Intent;
31 import android.os.Bundle;
32 import android.os.UserHandle;
33 import android.os.UserManager;
34 import android.util.Log;
35 import android.view.View;
36 import android.view.View.OnClickListener;
37 
38 import androidx.appcompat.app.AlertDialog;
39 import androidx.fragment.app.Fragment;
40 import androidx.preference.Preference;
41 import androidx.preference.PreferenceScreen;
42 
43 import com.android.settings.R;
44 import com.android.settings.core.PreferenceControllerMixin;
45 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
46 import com.android.settings.overlay.FeatureFactory;
47 import com.android.settings.widget.RestrictedButton;
48 import com.android.settingslib.core.AbstractPreferenceController;
49 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
50 import com.android.settingslib.widget.LayoutPreference;
51 
52 import java.io.IOException;
53 
54 public class RemoveAccountPreferenceController extends AbstractPreferenceController
55         implements PreferenceControllerMixin, OnClickListener {
56 
57     private static final String TAG = "RemoveAccountPrefController";
58     private static final String KEY_REMOVE_ACCOUNT = "remove_account";
59 
60     private final MetricsFeatureProvider mMetricsFeatureProvider;
61     private Account mAccount;
62     private Fragment mParentFragment;
63     private UserHandle mUserHandle;
64     private LayoutPreference mRemoveAccountPreference;
65     private RestrictedButton mRemoveAccountButton;
66 
RemoveAccountPreferenceController(Context context, Fragment parent)67     public RemoveAccountPreferenceController(Context context, Fragment parent) {
68         super(context);
69         mParentFragment = parent;
70         mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
71     }
72 
73     @Override
displayPreference(PreferenceScreen screen)74     public void displayPreference(PreferenceScreen screen) {
75         super.displayPreference(screen);
76         mRemoveAccountPreference = screen.findPreference(KEY_REMOVE_ACCOUNT);
77         mRemoveAccountButton = mRemoveAccountPreference.findViewById(R.id.button);
78         mRemoveAccountButton.setOnClickListener(this);
79     }
80 
81     @Override
updateState(Preference preference)82     public void updateState(Preference preference) {
83         super.updateState(preference);
84         mRemoveAccountButton.updateState();
85     }
86 
87     @Override
isAvailable()88     public boolean isAvailable() {
89         return true;
90     }
91 
92     @Override
getPreferenceKey()93     public String getPreferenceKey() {
94         return KEY_REMOVE_ACCOUNT;
95     }
96 
97     @Override
onClick(View v)98     public void onClick(View v) {
99         mMetricsFeatureProvider.logClickedPreference(mRemoveAccountPreference,
100                 mMetricsFeatureProvider.getMetricsCategory(mParentFragment));
101         ConfirmRemoveAccountDialog.show(mParentFragment, mAccount, mUserHandle);
102     }
103 
init(Account account, UserHandle userHandle)104     public void init(Account account, UserHandle userHandle) {
105         mAccount = account;
106         mUserHandle = userHandle;
107         mRemoveAccountButton.init(mUserHandle, UserManager.DISALLOW_MODIFY_ACCOUNTS);
108     }
109 
110     /**
111      * Dialog to confirm with user about account removal
112      */
113     public static class ConfirmRemoveAccountDialog extends InstrumentedDialogFragment implements
114             DialogInterface.OnClickListener {
115         private static final String KEY_ACCOUNT = "account";
116         private static final String REMOVE_ACCOUNT_DIALOG = "confirmRemoveAccount";
117         private Account mAccount;
118         private UserHandle mUserHandle;
119 
show( Fragment parent, Account account, UserHandle userHandle)120         public static ConfirmRemoveAccountDialog show(
121                 Fragment parent, Account account, UserHandle userHandle) {
122             if (!parent.isAdded()) {
123                 return null;
124             }
125             final ConfirmRemoveAccountDialog dialog = new ConfirmRemoveAccountDialog();
126             Bundle bundle = new Bundle();
127             bundle.putParcelable(KEY_ACCOUNT, account);
128             bundle.putParcelable(Intent.EXTRA_USER, userHandle);
129             dialog.setArguments(bundle);
130             dialog.setTargetFragment(parent, 0);
131             dialog.show(parent.getFragmentManager(), REMOVE_ACCOUNT_DIALOG);
132             return dialog;
133         }
134 
135         @Override
onCreate(Bundle savedInstanceState)136         public void onCreate(Bundle savedInstanceState) {
137             super.onCreate(savedInstanceState);
138             final Bundle arguments = getArguments();
139             mAccount = arguments.getParcelable(KEY_ACCOUNT);
140             mUserHandle = arguments.getParcelable(Intent.EXTRA_USER);
141         }
142 
143         @Override
onCreateDialog(Bundle savedInstanceState)144         public Dialog onCreateDialog(Bundle savedInstanceState) {
145             final Context context = getActivity();
146             return new AlertDialog.Builder(context)
147                     .setTitle(R.string.really_remove_account_title)
148                     .setMessage(R.string.really_remove_account_message)
149                     .setNegativeButton(android.R.string.cancel, null)
150                     .setPositiveButton(R.string.remove_account_label, this)
151                     .create();
152         }
153 
154         @Override
getMetricsCategory()155         public int getMetricsCategory() {
156             return SettingsEnums.DIALOG_ACCOUNT_SYNC_REMOVE;
157         }
158 
159         @Override
onClick(DialogInterface dialog, int which)160         public void onClick(DialogInterface dialog, int which) {
161             Activity activity = getTargetFragment().getActivity();
162             AccountManager.get(activity).removeAccountAsUser(mAccount, activity,
163                     future -> {
164                         final Activity targetActivity = getTargetFragment().getActivity();
165                         if (targetActivity == null || targetActivity.isFinishing()) {
166                             Log.w(TAG, "Activity is no longer alive, skipping results");
167                             return;
168                         }
169                         boolean failed = true;
170                         try {
171                             if (future.getResult()
172                                     .getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) {
173                                 failed = false;
174                             }
175                         } catch (OperationCanceledException
176                                 | IOException
177                                 | AuthenticatorException e) {
178                             // handled below
179                             Log.w(TAG, "Remove account error: " + e);
180                         }
181                         Log.i(TAG, "failed: " + failed);
182                         if (failed) {
183                             RemoveAccountFailureDialog.show(getTargetFragment());
184                         } else {
185                             targetActivity.finish();
186                         }
187                     }, null, mUserHandle);
188         }
189     }
190 
191     /**
192      * Dialog to tell user about account removal failure
193      */
194     public static class RemoveAccountFailureDialog extends InstrumentedDialogFragment {
195 
196         private static final String FAILED_REMOVAL_DIALOG = "removeAccountFailed";
197 
show(Fragment parent)198         public static void show(Fragment parent) {
199             if (!parent.isAdded()) {
200                 return;
201             }
202             final RemoveAccountFailureDialog dialog = new RemoveAccountFailureDialog();
203             dialog.setTargetFragment(parent, 0);
204             try {
205                 dialog.show(parent.getFragmentManager(), FAILED_REMOVAL_DIALOG);
206             } catch (IllegalStateException e) {
207                 Log.w(TAG, "Can't show RemoveAccountFailureDialog. " +  e.getMessage());
208             }
209         }
210 
211         @Override
onCreateDialog(Bundle savedInstanceState)212         public Dialog onCreateDialog(Bundle savedInstanceState) {
213             final Context context = getActivity();
214 
215             return new AlertDialog.Builder(context)
216                     .setTitle(R.string.remove_account_label)
217                     .setMessage(getContext().getSystemService(DevicePolicyManager.class)
218                             .getResources()
219                             .getString(REMOVE_ACCOUNT_FAILED_ADMIN_RESTRICTION,
220                                     () -> getString(R.string.remove_account_failed)))
221                     .setPositiveButton(android.R.string.ok, null)
222                     .create();
223         }
224 
225         @Override
getMetricsCategory()226         public int getMetricsCategory() {
227             return SettingsEnums.DIALOG_ACCOUNT_SYNC_FAILED_REMOVAL;
228         }
229 
230     }
231 }
232