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