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.car.settings.accounts;
17 
18 import android.accounts.Account;
19 import android.accounts.AccountManager;
20 import android.accounts.AccountManagerCallback;
21 import android.accounts.AuthenticatorException;
22 import android.accounts.OperationCanceledException;
23 import android.app.Activity;
24 import android.app.Dialog;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.pm.UserInfo;
29 import android.os.Bundle;
30 import android.os.UserHandle;
31 
32 import androidx.annotation.VisibleForTesting;
33 import androidx.annotation.XmlRes;
34 import androidx.fragment.app.Fragment;
35 
36 import com.android.car.settings.R;
37 import com.android.car.settings.common.ErrorDialog;
38 import com.android.car.settings.common.Logger;
39 import com.android.car.settings.common.SettingsFragment;
40 import com.android.car.settings.users.UserHelper;
41 import com.android.car.ui.AlertDialogBuilder;
42 import com.android.car.ui.preference.CarUiDialogFragment;
43 import com.android.car.ui.toolbar.MenuItem;
44 import com.android.settingslib.accounts.AuthenticatorHelper;
45 
46 import java.io.IOException;
47 import java.util.Arrays;
48 import java.util.Collections;
49 import java.util.List;
50 
51 /**
52  * Shows account details, and delete account option.
53  */
54 public class AccountDetailsFragment extends SettingsFragment implements
55         AuthenticatorHelper.OnAccountsUpdateListener {
56     public static final String EXTRA_ACCOUNT = "extra_account";
57     public static final String EXTRA_ACCOUNT_LABEL = "extra_account_label";
58     public static final String EXTRA_USER_INFO = "extra_user_info";
59 
60     private Account mAccount;
61     private UserInfo mUserInfo;
62     private AuthenticatorHelper mAuthenticatorHelper;
63     private MenuItem mRemoveButton;
64 
65     /**
66      * Creates a new AccountDetailsFragment.
67      *
68      * <p>Passes the provided account, label, and user info to the fragment via fragment arguments.
69      */
newInstance(Account account, CharSequence label, UserInfo userInfo)70     public static AccountDetailsFragment newInstance(Account account, CharSequence label,
71             UserInfo userInfo) {
72         AccountDetailsFragment
73                 accountDetailsFragment = new AccountDetailsFragment();
74         Bundle bundle = new Bundle();
75         bundle.putParcelable(EXTRA_ACCOUNT, account);
76         bundle.putCharSequence(EXTRA_ACCOUNT_LABEL, label);
77         bundle.putParcelable(EXTRA_USER_INFO, userInfo);
78         accountDetailsFragment.setArguments(bundle);
79         return accountDetailsFragment;
80     }
81 
82     @Override
83     @XmlRes
getPreferenceScreenResId()84     protected int getPreferenceScreenResId() {
85         return R.xml.account_details_fragment;
86     }
87 
88     @Override
getToolbarMenuItems()89     protected List<MenuItem> getToolbarMenuItems() {
90         return Collections.singletonList(mRemoveButton);
91     }
92 
93     @Override
onAttach(Context context)94     public void onAttach(Context context) {
95         super.onAttach(context);
96 
97         mAccount = getArguments().getParcelable(EXTRA_ACCOUNT);
98         mUserInfo = getArguments().getParcelable(EXTRA_USER_INFO);
99 
100         use(AccountDetailsPreferenceController.class, R.string.pk_account_details)
101                 .setAccount(mAccount);
102         use(AccountDetailsPreferenceController.class, R.string.pk_account_details)
103                 .setUserHandle(mUserInfo.getUserHandle());
104 
105         use(AccountSyncPreferenceController.class, R.string.pk_account_sync)
106                 .setAccount(mAccount);
107         use(AccountDetailsSettingController.class, R.string.pk_account_settings)
108                 .setAccount(mAccount);
109         use(AccountSyncPreferenceController.class, R.string.pk_account_sync)
110                 .setUserHandle(mUserInfo.getUserHandle());
111     }
112 
113     @Override
onCreate(Bundle savedInstanceState)114     public void onCreate(Bundle savedInstanceState) {
115         super.onCreate(savedInstanceState);
116 
117         boolean canModifyAccount = UserHelper.getInstance(getContext())
118                 .canCurrentProcessModifyAccounts();
119 
120         mRemoveButton = new MenuItem.Builder(getContext())
121                 .setTitle(R.string.remove_button)
122                 .setOnClickListener(i -> onRemoveAccountClicked())
123                 .setVisible(canModifyAccount)
124                 .build();
125     }
126 
127     @Override
onActivityCreated(Bundle savedInstanceState)128     public void onActivityCreated(Bundle savedInstanceState) {
129         super.onActivityCreated(savedInstanceState);
130 
131         // Set the fragment's title
132         getToolbar().setTitle(getArguments().getCharSequence(EXTRA_ACCOUNT_LABEL));
133     }
134 
135     @Override
onStart()136     public void onStart() {
137         super.onStart();
138 
139         mAuthenticatorHelper = new AuthenticatorHelper(getContext(), mUserInfo.getUserHandle(),
140                 this);
141         mAuthenticatorHelper.listenToAccountUpdates();
142     }
143 
144     @Override
onStop()145     public void onStop() {
146         super.onStop();
147         mAuthenticatorHelper.stopListeningToAccountUpdates();
148     }
149 
150     @Override
onAccountsUpdate(UserHandle userHandle)151     public void onAccountsUpdate(UserHandle userHandle) {
152         if (!accountExists()) {
153             // The account was deleted. Pop back.
154             goBack();
155         }
156     }
157 
158     /** Returns whether the account being shown by this fragment still exists. */
159     @VisibleForTesting
accountExists()160     boolean accountExists() {
161         if (mAccount == null) {
162             return false;
163         }
164 
165         Account[] accounts = AccountManager.get(getContext()).getAccountsByTypeAsUser(mAccount.type,
166                 mUserInfo.getUserHandle());
167 
168         return Arrays.asList(accounts).contains(mAccount);
169     }
170 
onRemoveAccountClicked()171     private void onRemoveAccountClicked() {
172         ConfirmRemoveAccountDialogFragment.show(this, mAccount, mUserInfo.getUserHandle());
173     }
174 
175     /**
176      * Dialog to confirm with user about account removal
177      */
178     public static class ConfirmRemoveAccountDialogFragment extends CarUiDialogFragment {
179         private static final String KEY_ACCOUNT = "account";
180         private static final String DIALOG_TAG = "confirmRemoveAccount";
181         private static final Logger LOG = new Logger(ConfirmRemoveAccountDialogFragment.class);
182         private final AccountManagerCallback<Bundle> mCallback =
183                 future -> {
184                     // If already out of this screen, don't proceed.
185                     if (!getTargetFragment().isResumed()) {
186                         return;
187                     }
188 
189                     boolean success = false;
190                     try {
191                         success =
192                                 future.getResult().getBoolean(
193                                         AccountManager.KEY_BOOLEAN_RESULT);
194                     } catch (OperationCanceledException | IOException | AuthenticatorException e) {
195                         LOG.v("removeAccount error: " + e);
196                     }
197                     final Activity activity = getTargetFragment().getActivity();
198                     if (!success && activity != null && !activity.isFinishing()) {
199                         ErrorDialog.show(getTargetFragment(),
200                                 R.string.remove_account_error_title);
201                     } else {
202                         getTargetFragment().getFragmentManager().popBackStack();
203                     }
204                 };
205         private Account mAccount;
206         private UserHandle mUserHandle;
207 
show( Fragment parent, Account account, UserHandle userHandle)208         public static void show(
209                 Fragment parent, Account account, UserHandle userHandle) {
210             final ConfirmRemoveAccountDialogFragment dialog =
211                     new ConfirmRemoveAccountDialogFragment();
212             Bundle bundle = new Bundle();
213             bundle.putParcelable(KEY_ACCOUNT, account);
214             bundle.putParcelable(Intent.EXTRA_USER, userHandle);
215             dialog.setArguments(bundle);
216             dialog.setTargetFragment(parent, 0);
217             dialog.show(parent.getFragmentManager(), DIALOG_TAG);
218         }
219 
220         @Override
onCreate(Bundle savedInstanceState)221         public void onCreate(Bundle savedInstanceState) {
222             super.onCreate(savedInstanceState);
223             final Bundle arguments = getArguments();
224             mAccount = arguments.getParcelable(KEY_ACCOUNT);
225             mUserHandle = arguments.getParcelable(Intent.EXTRA_USER);
226         }
227 
228         @Override
onCreateDialog(Bundle savedInstanceState)229         public Dialog onCreateDialog(Bundle savedInstanceState) {
230             return new AlertDialogBuilder(getContext())
231                     .setTitle(R.string.really_remove_account_title)
232                     .setMessage(R.string.really_remove_account_message)
233                     .setNegativeButton(android.R.string.cancel, null)
234                     .setPositiveButton(R.string.remove_account_title, this)
235                     .create();
236         }
237 
238         @Override
onClick(DialogInterface dialog, int which)239         public void onClick(DialogInterface dialog, int which) {
240             Activity activity = getTargetFragment().getActivity();
241             AccountManager.get(activity).removeAccountAsUser(
242                     mAccount, activity, mCallback, null, mUserHandle);
243             dialog.dismiss();
244         }
245 
246         @Override
onDialogClosed(boolean positiveResult)247         public void onDialogClosed(boolean positiveResult) {}
248     }
249 }
250