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 android.car.drivingstate.CarUxRestrictions;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.graphics.drawable.Drawable;
23 import android.os.Handler;
24 
25 import androidx.annotation.Nullable;
26 import androidx.annotation.VisibleForTesting;
27 import androidx.collection.ArrayMap;
28 import androidx.preference.PreferenceGroup;
29 
30 import com.android.car.settings.common.ActivityResultCallback;
31 import com.android.car.settings.common.FragmentController;
32 import com.android.car.settings.common.PreferenceController;
33 import com.android.car.ui.preference.CarUiPreference;
34 import com.android.settingslib.accounts.AuthenticatorHelper;
35 
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.Comparator;
39 import java.util.HashSet;
40 import java.util.List;
41 import java.util.Set;
42 
43 /**
44  * Controller for showing the user the list of accounts they can add.
45  *
46  * <p>Largely derived from {@link com.android.settings.accounts.ChooseAccountActivity}
47  */
48 public class ChooseAccountPreferenceController extends
49         PreferenceController<PreferenceGroup> implements ActivityResultCallback {
50     @VisibleForTesting
51     static final int ADD_ACCOUNT_REQUEST_CODE = 100;
52 
53     private AccountTypesHelper mAccountTypesHelper;
54     private ArrayMap<String, AuthenticatorDescriptionPreference> mPreferences = new ArrayMap<>();
55     private boolean mIsStarted = false;
56     private boolean mHasPendingBack = false;
57 
ChooseAccountPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)58     public ChooseAccountPreferenceController(Context context, String preferenceKey,
59             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
60         super(context, preferenceKey, fragmentController, uxRestrictions);
61         mAccountTypesHelper = new AccountTypesHelper(context);
62         mAccountTypesHelper.setOnChangeListener(this::forceUpdateAccountsCategory);
63     }
64 
65     /** Sets the authorities that the user has. */
setAuthorities(List<String> authorities)66     public void setAuthorities(List<String> authorities) {
67         mAccountTypesHelper.setAuthorities(authorities);
68     }
69 
70     /** Sets the filter for accounts that should be shown. */
setAccountTypesFilter(Set<String> accountTypesFilter)71     public void setAccountTypesFilter(Set<String> accountTypesFilter) {
72         mAccountTypesHelper.setAccountTypesFilter(accountTypesFilter);
73     }
74 
75     /** Sets the filter for accounts that should NOT be shown. */
setAccountTypesExclusionFilter(Set<String> accountTypesExclusionFilterFilter)76     protected void setAccountTypesExclusionFilter(Set<String> accountTypesExclusionFilterFilter) {
77         mAccountTypesHelper.setAccountTypesExclusionFilter(accountTypesExclusionFilterFilter);
78     }
79 
80     @Override
getPreferenceType()81     protected Class<PreferenceGroup> getPreferenceType() {
82         return PreferenceGroup.class;
83     }
84 
85     @Override
updateState(PreferenceGroup preferenceGroup)86     protected void updateState(PreferenceGroup preferenceGroup) {
87         mAccountTypesHelper.forceUpdate();
88     }
89 
90     /**
91      * Registers the account update callback.
92      */
93     @Override
onStartInternal()94     protected void onStartInternal() {
95         mIsStarted = true;
96         mAccountTypesHelper.listenToAccountUpdates();
97 
98         if (mHasPendingBack) {
99             mHasPendingBack = false;
100 
101             // Post the fragment navigation because FragmentManager may still be executing
102             // transactions during onStart.
103             new Handler().post(() -> getFragmentController().goBack());
104         }
105     }
106 
107     /**
108      * Unregisters the account update callback.
109      */
110     @Override
onStopInternal()111     protected void onStopInternal() {
112         mAccountTypesHelper.stopListeningToAccountUpdates();
113         mIsStarted = false;
114     }
115 
116     /** Forces a refresh of the authenticator description preferences. */
forceUpdateAccountsCategory()117     private void forceUpdateAccountsCategory() {
118         Set<String> preferencesToRemove = new HashSet<>(mPreferences.keySet());
119         List<AuthenticatorDescriptionPreference> preferences =
120                 getAuthenticatorDescriptionPreferences(preferencesToRemove);
121         // Add all preferences that aren't already shown
122         for (int i = 0; i < preferences.size(); i++) {
123             AuthenticatorDescriptionPreference preference = preferences.get(i);
124             preference.setOrder(i);
125             String key = preference.getKey();
126             getPreference().addPreference(preference);
127             mPreferences.put(key, preference);
128         }
129 
130         // Remove all preferences that should no longer be shown
131         for (String key : preferencesToRemove) {
132             getPreference().removePreference(mPreferences.get(key));
133             mPreferences.remove(key);
134         }
135     }
136 
137     /**
138      * Returns a list of preferences corresponding to the account types the user can add.
139      *
140      * <p> Derived from
141      * {@link com.android.settings.accounts.ChooseAccountActivity#onAuthDescriptionsUpdated}
142      *
143      * @param preferencesToRemove the current preferences shown; will contain the preferences that
144      *                            need to be removed from the screen after method execution
145      */
getAuthenticatorDescriptionPreferences( Set<String> preferencesToRemove)146     private List<AuthenticatorDescriptionPreference> getAuthenticatorDescriptionPreferences(
147             Set<String> preferencesToRemove) {
148         ArrayList<AuthenticatorDescriptionPreference> authenticatorDescriptionPreferences =
149                 new ArrayList<>();
150         Set<String> authorizedAccountTypes = mAccountTypesHelper.getAuthorizedAccountTypes();
151         // Create list of account providers to show on page.
152         for (String accountType : authorizedAccountTypes) {
153             CharSequence label = mAccountTypesHelper.getLabelForType(accountType);
154             Drawable icon = mAccountTypesHelper.getDrawableForType(accountType);
155 
156             // Add a preference for the provider to the list and remove it from preferencesToRemove.
157             AuthenticatorDescriptionPreference preference = mPreferences.getOrDefault(accountType,
158                     new AuthenticatorDescriptionPreference(getContext(), accountType, label, icon));
159             preference.setOnPreferenceClickListener(
160                     pref -> {
161                         Intent intent = AddAccountActivity.createAddAccountActivityIntent(
162                                 getContext(), preference.getAccountType());
163                         getFragmentController().startActivityForResult(intent,
164                                 ADD_ACCOUNT_REQUEST_CODE, /* callback= */ this);
165                         return true;
166                     });
167             authenticatorDescriptionPreferences.add(preference);
168             preferencesToRemove.remove(accountType);
169         }
170 
171         Collections.sort(authenticatorDescriptionPreferences, Comparator.comparing(
172                 (AuthenticatorDescriptionPreference a) -> a.getTitle().toString()));
173 
174         return authenticatorDescriptionPreferences;
175     }
176 
177     /** Used for testing to trigger an account update. */
178     @VisibleForTesting
getAuthenticatorHelper()179     AuthenticatorHelper getAuthenticatorHelper() {
180         return mAccountTypesHelper.getAuthenticatorHelper();
181     }
182 
183     @Override
processActivityResult(int requestCode, int resultCode, @Nullable Intent data)184     public void processActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
185         if (requestCode == ADD_ACCOUNT_REQUEST_CODE) {
186             if (mIsStarted) {
187                 getFragmentController().goBack();
188             } else {
189                 mHasPendingBack = true;
190             }
191         }
192     }
193 
194     /** Handles adding accounts. */
195     interface AddAccountListener {
196         /** Handles adding an account. */
addAccount(String accountType)197         void addAccount(String accountType);
198     }
199 
200     private static class AuthenticatorDescriptionPreference extends CarUiPreference {
201         private final String mType;
202 
AuthenticatorDescriptionPreference(Context context, String accountType, CharSequence label, Drawable icon)203         AuthenticatorDescriptionPreference(Context context, String accountType, CharSequence label,
204                 Drawable icon) {
205             super(context);
206             mType = accountType;
207 
208             setKey(accountType);
209             setTitle(label);
210             setIcon(icon);
211             setShowChevron(false);
212         }
213 
getAccountType()214         private String getAccountType() {
215             return mType;
216         }
217     }
218 }
219