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.tv.settings.accounts;
18 
19 import static com.android.tv.settings.util.InstrumentationUtils.logEntrySelected;
20 
21 import android.accounts.Account;
22 import android.accounts.AccountManager;
23 import android.accounts.AuthenticatorDescription;
24 import android.app.tvsettings.TvSettingsEnums;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.PackageManager;
29 import android.content.res.Resources;
30 import android.graphics.drawable.Drawable;
31 import android.os.Bundle;
32 import android.os.UserHandle;
33 import android.text.TextUtils;
34 import android.util.ArraySet;
35 import android.util.Log;
36 
37 import androidx.annotation.Keep;
38 import androidx.preference.Preference;
39 import androidx.preference.PreferenceScreen;
40 
41 import com.android.internal.logging.nano.MetricsProto;
42 import com.android.settingslib.accounts.AuthenticatorHelper;
43 import com.android.tv.settings.R;
44 import com.android.tv.settings.SettingsPreferenceFragment;
45 import com.android.tv.settings.system.SecurityFragment;
46 
47 import java.util.ArrayList;
48 import java.util.Set;
49 
50 /**
51  * The "Accounts and Sign in" screen in TV settings.
52  */
53 @Keep
54 public class AccountsFragment extends SettingsPreferenceFragment {
55     private static final String TAG = "AccountsFragment";
56     private static final String KEY_ADD_ACCOUNT = "add_account";
57     private AuthenticatorHelper mAuthenticatorHelper;
58 
59     @Override
onCreate(Bundle savedInstanceState)60     public void onCreate(Bundle savedInstanceState) {
61         mAuthenticatorHelper = new AuthenticatorHelper(getContext(),
62                 new UserHandle(UserHandle.myUserId()), userHandle -> updateAccounts());
63         super.onCreate(savedInstanceState);
64     }
65 
66     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)67     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
68         setPreferencesFromResource(R.xml.accounts, null);
69     }
70 
71     @Override
onStart()72     public void onStart() {
73         super.onStart();
74         mAuthenticatorHelper.listenToAccountUpdates();
75     }
76 
77     @Override
onStop()78     public void onStop() {
79         super.onStop();
80         mAuthenticatorHelper.stopListeningToAccountUpdates();
81     }
82 
83     @Override
onResume()84     public void onResume() {
85         super.onResume();
86         updateAccounts();
87     }
88 
updateAccounts()89     private void updateAccounts() {
90         PreferenceScreen prefScreen = getPreferenceScreen();
91         final Set<String> touchedAccounts = new ArraySet<>(prefScreen.getPreferenceCount());
92 
93         final AccountManager am = AccountManager.get(getContext());
94         final AuthenticatorDescription[] authTypes = am.getAuthenticatorTypes();
95         final ArrayList<String> allowableAccountTypes = new ArrayList<>(authTypes.length);
96         final Context themedContext = getPreferenceManager().getContext();
97 
98         for (AuthenticatorDescription authDesc : authTypes) {
99             Context targetContext = getTargetContext(getContext(), authDesc);
100             if (targetContext == null) {
101                 continue;
102             }
103 
104             String authTitle = getAuthTitle(targetContext, authDesc);
105 
106 
107             Account[] accounts = am.getAccountsByType(authDesc.type);
108             if (accounts == null || accounts.length == 0) {
109                 continue;  // No point in continuing; there aren't any accounts to show.
110             }
111 
112             Drawable authImage = getAuthImage(targetContext, authDesc);
113 
114             // Display an entry for each installed account we have.
115             for (final Account account : accounts) {
116                 final String key = "account_pref:" + account.type + ":" + account.name;
117                 Preference preference = findPreference(key);
118                 if (preference == null) {
119                     preference = new Preference(themedContext);
120                 }
121                 preference.setTitle(authTitle != null ? authTitle : account.name);
122                 preference.setIcon(authImage);
123                 preference.setSummary(authTitle != null ? account.name : null);
124                 preference.setFragment(AccountSyncFragment.class.getName());
125                 AccountSyncFragment.prepareArgs(preference.getExtras(), account);
126 
127                 touchedAccounts.add(key);
128                 preference.setKey(key);
129 
130                 prefScreen.addPreference(preference);
131             }
132         }
133 
134         for (int i = 0; i < prefScreen.getPreferenceCount();) {
135             final Preference preference = prefScreen.getPreference(i);
136             final String key = preference.getKey();
137             if (touchedAccounts.contains(key) || TextUtils.equals(KEY_ADD_ACCOUNT, key)) {
138                 i++;
139             } else {
140                 prefScreen.removePreference(preference);
141             }
142         }
143 
144         // Never allow restricted profile to add accounts.
145         final Preference addAccountPref = findPreference(KEY_ADD_ACCOUNT);
146         if (addAccountPref != null) {
147             addAccountPref.setOrder(Integer.MAX_VALUE);
148             if (isRestricted()) {
149                 addAccountPref.setVisible(false);
150             } else {
151                 setUpAddAccountPrefIntent(addAccountPref, getContext());
152             }
153         }
154     }
155 
isRestricted()156     private boolean isRestricted() {
157         return SecurityFragment.isRestrictedProfileInEffect(getContext());
158     }
159 
160     @Override
getMetricsCategory()161     public int getMetricsCategory() {
162         return  MetricsProto.MetricsEvent.ACCOUNTS_MANAGE_ACCOUNTS;
163     }
164 
165     /**
166      * Set up the intent and visibility for the given preference based on the information from
167      * AccountManager.
168      */
setUpAddAccountPrefIntent(Preference preference, Context context)169     public static void setUpAddAccountPrefIntent(Preference preference, Context context) {
170         final AccountManager am = AccountManager.get(context);
171         final AuthenticatorDescription[] authTypes = am.getAuthenticatorTypes();
172         final ArrayList<String> allowableAccountTypes = new ArrayList<>(authTypes.length);
173         for (AuthenticatorDescription authDesc : authTypes) {
174             final Context targetContext = getTargetContext(context, authDesc);
175             if (targetContext == null) {
176                 continue;
177             }
178             String authTitle = getAuthTitle(targetContext, authDesc);
179             if (authTitle != null || authDesc.iconId != 0) {
180                 allowableAccountTypes.add(authDesc.type);
181             }
182         }
183 
184         Intent i = new Intent().setComponent(new ComponentName("com.android.tv.settings",
185                 "com.android.tv.settings.accounts.AddAccountWithTypeActivity"));
186         i.putExtra(AddAccountWithTypeActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
187                 allowableAccountTypes.toArray(new String[allowableAccountTypes.size()]));
188 
189                 // If there are available account types, show the "add account" button.
190         preference.setVisible(!allowableAccountTypes.isEmpty());
191         preference.setIntent(i);
192         preference.setOnPreferenceClickListener(
193                 preference1 -> {
194                     logEntrySelected(TvSettingsEnums.ACCOUNT_CLASSIC_ADD_ACCOUNT);
195                     return false;
196                 });
197     }
198 
getTargetContext(Context context, AuthenticatorDescription authDesc)199     private static Context getTargetContext(Context context, AuthenticatorDescription authDesc) {
200         Context targetContext = null;
201         try {
202             targetContext = context.createPackageContext(authDesc.packageName, 0);
203         } catch (PackageManager.NameNotFoundException e) {
204             Log.e(TAG, "Authenticator description with bad package name", e);
205         } catch (SecurityException e) {
206             Log.e(TAG, "Security exception loading package resources", e);
207         }
208         return targetContext;
209     }
210 
getAuthTitle(Context targetContext, AuthenticatorDescription authDesc)211     private static String getAuthTitle(Context targetContext, AuthenticatorDescription authDesc) {
212         // Main title text comes from the authenticator description (e.g. "Google").
213         String authTitle = null;
214         try {
215             authTitle = targetContext.getString(authDesc.labelId);
216             if (TextUtils.isEmpty(authTitle)) {
217                 authTitle = null;  // Handled later when we add the row.
218             }
219         } catch (Resources.NotFoundException e) {
220             Log.e(TAG, "Authenticator description with bad label id", e);
221         }
222         return authTitle;
223     }
224 
getAuthImage(Context targetContext, AuthenticatorDescription authDesc)225     private static Drawable getAuthImage(Context targetContext, AuthenticatorDescription authDesc) {
226         // Icon URI to be displayed for each account is based on the type of authenticator.
227         Drawable authImage = null;
228         try {
229             authImage = targetContext.getDrawable(authDesc.iconId);
230         } catch (Resources.NotFoundException e) {
231             Log.e(TAG, "Authenticator has bad resources", e);
232         }
233         return authImage;
234     }
235 
236     @Override
getPageId()237     protected int getPageId() {
238         return TvSettingsEnums.ACCOUNT_CLASSIC;
239     }
240 }
241