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.HashSet;
39 import java.util.List;
40 import java.util.Set;
41 
42 /**
43  * Controller for showing the user the list of accounts they can add.
44  *
45  * <p>Largely derived from {@link com.android.settings.accounts.ChooseAccountActivity}
46  */
47 public class ChooseAccountPreferenceController extends
48         PreferenceController<PreferenceGroup> implements ActivityResultCallback {
49     @VisibleForTesting
50     static final int ADD_ACCOUNT_REQUEST_CODE = 100;
51 
52     private AccountTypesHelper mAccountTypesHelper;
53     private ArrayMap<String, AuthenticatorDescriptionPreference> mPreferences = new ArrayMap<>();
54     private boolean mHasPendingBack = false;
55 
ChooseAccountPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)56     public ChooseAccountPreferenceController(Context context, String preferenceKey,
57             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
58         super(context, preferenceKey, fragmentController, uxRestrictions);
59         mAccountTypesHelper = new AccountTypesHelper(context);
60         mAccountTypesHelper.setOnChangeListener(this::forceUpdateAccountsCategory);
61     }
62 
63     /** Sets the authorities that the user has. */
setAuthorities(List<String> authorities)64     public void setAuthorities(List<String> authorities) {
65         mAccountTypesHelper.setAuthorities(authorities);
66     }
67 
68     /** Sets the filter for accounts that should be shown. */
setAccountTypesFilter(Set<String> accountTypesFilter)69     public void setAccountTypesFilter(Set<String> accountTypesFilter) {
70         mAccountTypesHelper.setAccountTypesFilter(accountTypesFilter);
71     }
72 
73     /** Sets the filter for accounts that should NOT be shown. */
setAccountTypesExclusionFilter(Set<String> accountTypesExclusionFilterFilter)74     protected void setAccountTypesExclusionFilter(Set<String> accountTypesExclusionFilterFilter) {
75         mAccountTypesHelper.setAccountTypesExclusionFilter(accountTypesExclusionFilterFilter);
76     }
77 
78     @Override
getPreferenceType()79     protected Class<PreferenceGroup> getPreferenceType() {
80         return PreferenceGroup.class;
81     }
82 
83     @Override
updateState(PreferenceGroup preferenceGroup)84     protected void updateState(PreferenceGroup preferenceGroup) {
85         mAccountTypesHelper.forceUpdate();
86     }
87 
88     /**
89      * Registers the account update callback.
90      */
91     @Override
onStartInternal()92     protected void onStartInternal() {
93         mAccountTypesHelper.listenToAccountUpdates();
94 
95         if (mHasPendingBack) {
96             mHasPendingBack = false;
97 
98             // Post the fragment navigation because FragmentManager may still be executing
99             // transactions during onStart.
100             new Handler().post(() -> getFragmentController().goBack());
101         }
102     }
103 
104     /**
105      * Unregisters the account update callback.
106      */
107     @Override
onStopInternal()108     protected void onStopInternal() {
109         mAccountTypesHelper.stopListeningToAccountUpdates();
110     }
111 
112     /** Forces a refresh of the authenticator description preferences. */
forceUpdateAccountsCategory()113     private void forceUpdateAccountsCategory() {
114         Set<String> preferencesToRemove = new HashSet<>(mPreferences.keySet());
115         List<AuthenticatorDescriptionPreference> preferences =
116                 getAuthenticatorDescriptionPreferences(preferencesToRemove);
117         // Add all preferences that aren't already shown
118         for (int i = 0; i < preferences.size(); i++) {
119             AuthenticatorDescriptionPreference preference = preferences.get(i);
120             preference.setOrder(i);
121             String key = preference.getKey();
122             getPreference().addPreference(preference);
123             mPreferences.put(key, preference);
124         }
125 
126         // Remove all preferences that should no longer be shown
127         for (String key : preferencesToRemove) {
128             getPreference().removePreference(mPreferences.get(key));
129             mPreferences.remove(key);
130         }
131     }
132 
133     /**
134      * Returns a list of preferences corresponding to the account types the user can add.
135      *
136      * <p> Derived from
137      * {@link com.android.settings.accounts.ChooseAccountActivity#onAuthDescriptionsUpdated}
138      *
139      * @param preferencesToRemove the current preferences shown; will contain the preferences that
140      *                            need to be removed from the screen after method execution
141      */
getAuthenticatorDescriptionPreferences( Set<String> preferencesToRemove)142     private List<AuthenticatorDescriptionPreference> getAuthenticatorDescriptionPreferences(
143             Set<String> preferencesToRemove) {
144         ArrayList<AuthenticatorDescriptionPreference> authenticatorDescriptionPreferences =
145                 new ArrayList<>();
146         Set<String> authorizedAccountTypes = mAccountTypesHelper.getAuthorizedAccountTypes();
147         // Create list of account providers to show on page.
148         for (String accountType : authorizedAccountTypes) {
149             CharSequence label = mAccountTypesHelper.getLabelForType(accountType);
150             Drawable icon = mAccountTypesHelper.getDrawableForType(accountType);
151 
152             // Add a preference for the provider to the list and remove it from preferencesToRemove.
153             AuthenticatorDescriptionPreference preference = mPreferences.getOrDefault(accountType,
154                     new AuthenticatorDescriptionPreference(getContext(), accountType, label, icon));
155             preference.setOnPreferenceClickListener(
156                     pref -> {
157                         Intent intent = AddAccountActivity.createAddAccountActivityIntent(
158                                 getContext(), preference.getAccountType());
159                         getFragmentController().startActivityForResult(intent,
160                                 ADD_ACCOUNT_REQUEST_CODE, /* callback= */ this);
161                         return true;
162                     });
163             authenticatorDescriptionPreferences.add(preference);
164             preferencesToRemove.remove(accountType);
165         }
166 
167         Collections.sort(authenticatorDescriptionPreferences);
168 
169         return authenticatorDescriptionPreferences;
170     }
171 
172     /** Used for testing to trigger an account update. */
173     @VisibleForTesting
getAccountTypesHelper()174     AccountTypesHelper getAccountTypesHelper() {
175         return mAccountTypesHelper;
176     }
177 
178     @VisibleForTesting
setAuthenticatorHelper(AuthenticatorHelper helper)179     void setAuthenticatorHelper(AuthenticatorHelper helper) {
180         mAccountTypesHelper.setAuthenticatorHelper(helper);
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 (isStarted()) {
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