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