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