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