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