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