• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 package com.android.contacts.model.account;
17 
18 import static com.android.contacts.util.DeviceLocalAccountTypeFactory.Util.isLocalAccountType;
19 
20 import android.accounts.AccountManager;
21 import android.accounts.AuthenticatorDescription;
22 import android.content.ContentResolver;
23 import android.content.Context;
24 import android.content.SyncAdapterType;
25 import android.provider.ContactsContract;
26 import android.text.TextUtils;
27 import android.util.Log;
28 
29 import com.android.contacts.util.DeviceLocalAccountTypeFactory;
30 import com.android.contactsbind.ObjectFactory;
31 import com.google.common.base.Objects;
32 import com.google.common.collect.ImmutableList;
33 import com.google.common.collect.ImmutableMap;
34 
35 import java.util.Collections;
36 import java.util.HashSet;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Set;
40 import java.util.concurrent.ConcurrentHashMap;
41 import java.util.concurrent.ConcurrentMap;
42 
43 /**
44  * Provides access to {@link AccountType}s with contact data
45  *
46  * This class parses the contacts.xml for third-party accounts and caches the result.
47  * This means that {@link AccountTypeProvider#getAccountTypes(String)}} should be called from a
48  * background thread.
49  */
50 public class AccountTypeProvider {
51     private static final String TAG = "AccountTypeProvider";
52 
53     private final Context mContext;
54     private final DeviceLocalAccountTypeFactory mLocalAccountTypeFactory;
55     private final ImmutableMap<String, AuthenticatorDescription> mAuthTypes;
56 
57     private final ConcurrentMap<String, List<AccountType>> mCache = new ConcurrentHashMap<>();
58 
AccountTypeProvider(Context context)59     public AccountTypeProvider(Context context) {
60         this(context,
61                 ObjectFactory.getDeviceLocalAccountTypeFactory(context),
62                 ContentResolver.getSyncAdapterTypes(),
63                 ((AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE))
64                         .getAuthenticatorTypes());
65     }
66 
AccountTypeProvider(Context context, DeviceLocalAccountTypeFactory localTypeFactory, SyncAdapterType[] syncAdapterTypes, AuthenticatorDescription[] authenticatorDescriptions)67     public AccountTypeProvider(Context context, DeviceLocalAccountTypeFactory localTypeFactory,
68             SyncAdapterType[] syncAdapterTypes,
69             AuthenticatorDescription[] authenticatorDescriptions) {
70         mContext = context;
71         mLocalAccountTypeFactory = localTypeFactory;
72 
73         mAuthTypes = onlyContactSyncable(authenticatorDescriptions, syncAdapterTypes);
74     }
75 
76     /**
77      * Returns all account types associated with the provided type
78      *
79      * <p>There are many {@link AccountType}s for each accountType because {@AccountType} includes
80      * a dataSet and accounts can declare extension packages in contacts.xml that provide additional
81      * data sets for a particular type
82      * </p>
83      */
getAccountTypes(String accountType)84     public List<AccountType> getAccountTypes(String accountType) {
85         // ConcurrentHashMap doesn't support null keys
86         if (accountType == null) {
87             AccountType type = mLocalAccountTypeFactory.getAccountType(accountType);
88             // Just in case the DeviceLocalAccountTypeFactory doesn't handle the null type
89             if (type == null) {
90                 type = new FallbackAccountType(mContext);
91             }
92             return Collections.singletonList(type);
93         }
94 
95         List<AccountType> types = mCache.get(accountType);
96         if (types == null) {
97             types = loadTypes(accountType);
98             mCache.put(accountType, types);
99         }
100         return types;
101     }
102 
hasTypeForAccount(AccountWithDataSet account)103     public boolean hasTypeForAccount(AccountWithDataSet account) {
104         return getTypeForAccount(account) != null;
105     }
106 
hasTypeWithDataset(String type, String dataSet)107     public boolean hasTypeWithDataset(String type, String dataSet) {
108         // getAccountTypes() never returns null
109         final List<AccountType> accountTypes = getAccountTypes(type);
110         for (AccountType accountType : accountTypes) {
111             if (Objects.equal(accountType.dataSet, dataSet)) {
112                 return true;
113             }
114         }
115         return false;
116     }
117 
118     /**
119      * Returns the AccountType with the matching type and dataSet or null if no account with those
120      * members exists
121      */
getType(String type, String dataSet)122     public AccountType getType(String type, String dataSet) {
123         final List<AccountType> accountTypes = getAccountTypes(type);
124         for (AccountType accountType : accountTypes) {
125             if (Objects.equal(accountType.dataSet, dataSet)) {
126                 return accountType;
127             }
128         }
129         return null;
130     }
131 
132     /**
133      * Returns the AccountType for a particular account or null if no account type exists for the
134      * account
135      */
getTypeForAccount(AccountWithDataSet account)136     public AccountType getTypeForAccount(AccountWithDataSet account) {
137         return getType(account.type, account.dataSet);
138     }
139 
shouldUpdate(AuthenticatorDescription[] auths, SyncAdapterType[] syncTypes)140     public boolean shouldUpdate(AuthenticatorDescription[] auths, SyncAdapterType[] syncTypes) {
141         Map<String, AuthenticatorDescription> contactsAuths = onlyContactSyncable(auths, syncTypes);
142         if (!contactsAuths.keySet().equals(mAuthTypes.keySet())) {
143             return true;
144         }
145         for (AuthenticatorDescription auth : contactsAuths.values()) {
146             if (!deepEquals(mAuthTypes.get(auth.type), auth)) {
147                 return true;
148             }
149         }
150         return false;
151     }
152 
supportsContactsSyncing(String accountType)153     public boolean supportsContactsSyncing(String accountType) {
154         return mAuthTypes.containsKey(accountType);
155     }
156 
loadTypes(String type)157     private List<AccountType> loadTypes(String type) {
158         final AuthenticatorDescription auth = mAuthTypes.get(type);
159         if (auth == null) {
160             if (Log.isLoggable(TAG, Log.DEBUG)) {
161                 Log.d(TAG, "Null auth type for " + type);
162             }
163             return Collections.emptyList();
164         }
165 
166         AccountType accountType;
167         if (GoogleAccountType.ACCOUNT_TYPE.equals(type)) {
168             accountType = new GoogleAccountType(mContext, auth.packageName);
169         } else if (ExchangeAccountType.isExchangeType(type)) {
170             accountType = new ExchangeAccountType(mContext, auth.packageName, type);
171         } else if (SamsungAccountType.isSamsungAccountType(mContext, type,
172                 auth.packageName)) {
173             accountType = new SamsungAccountType(mContext, auth.packageName, type);
174         } else if (!ExternalAccountType.hasContactsXml(mContext, auth.packageName)
175                 && isLocalAccountType(mLocalAccountTypeFactory, type)) {
176             if (Log.isLoggable(TAG, Log.DEBUG)) {
177                 Log.d(TAG, "Registering local account type=" + type
178                         + ", packageName=" + auth.packageName);
179             }
180             accountType = mLocalAccountTypeFactory.getAccountType(type);
181         } else {
182             if (Log.isLoggable(TAG, Log.DEBUG)) {
183                 Log.d(TAG, "Registering external account type=" + type
184                         + ", packageName=" + auth.packageName);
185             }
186             accountType = new ExternalAccountType(mContext, auth.packageName, false);
187         }
188         if (!accountType.isInitialized()) {
189             if (accountType.isEmbedded()) {
190                 throw new IllegalStateException("Problem initializing embedded type "
191                         + accountType.getClass().getCanonicalName());
192             } else {
193                 // Skip external account types that couldn't be initialized
194                 if (Log.isLoggable(TAG, Log.DEBUG)) {
195                     Log.d(TAG, "Skipping external account type=" + type
196                             + ", packageName=" + auth.packageName);
197                 }
198                 return Collections.emptyList();
199             }
200         }
201 
202         accountType.initializeFieldsFromAuthenticator(auth);
203 
204         final ImmutableList.Builder<AccountType> result = ImmutableList.builder();
205         result.add(accountType);
206 
207         for (String extensionPackage : accountType.getExtensionPackageNames()) {
208             final ExternalAccountType extensionType =
209                     new ExternalAccountType(mContext, extensionPackage, true);
210             if (!extensionType.isInitialized()) {
211                 // Skip external account types that couldn't be initialized.
212                 continue;
213             }
214             if (!extensionType.hasContactsMetadata()) {
215                 Log.w(TAG, "Skipping extension package " + extensionPackage + " because"
216                         + " it doesn't have the CONTACTS_STRUCTURE metadata");
217                 continue;
218             }
219             if (TextUtils.isEmpty(extensionType.accountType)) {
220                 Log.w(TAG, "Skipping extension package " + extensionPackage + " because"
221                         + " the CONTACTS_STRUCTURE metadata doesn't have the accountType"
222                         + " attribute");
223                 continue;
224             }
225             if (!Objects.equal(extensionType.accountType, type)) {
226                 Log.w(TAG, "Skipping extension package " + extensionPackage + " because"
227                         + " the account type + " + extensionType.accountType +
228                         " doesn't match expected type " + type);
229                 continue;
230             }
231             if (Log.isLoggable(TAG, Log.DEBUG)) {
232                 Log.d(TAG, "Registering extension package account type="
233                         + accountType.accountType + ", dataSet=" + accountType.dataSet
234                         + ", packageName=" + extensionPackage);
235             }
236 
237             result.add(extensionType);
238         }
239         return result.build();
240     }
241 
onlyContactSyncable( AuthenticatorDescription[] auths, SyncAdapterType[] syncTypes)242     private static ImmutableMap<String, AuthenticatorDescription> onlyContactSyncable(
243             AuthenticatorDescription[] auths, SyncAdapterType[] syncTypes) {
244         final Set<String> mContactSyncableTypes = new HashSet<>();
245         for (SyncAdapterType type : syncTypes) {
246             if (type.authority.equals(ContactsContract.AUTHORITY)) {
247                 mContactSyncableTypes.add(type.accountType);
248             }
249         }
250 
251         final ImmutableMap.Builder<String, AuthenticatorDescription> builder =
252                 ImmutableMap.builder();
253         for (AuthenticatorDescription auth : auths) {
254             if (mContactSyncableTypes.contains(auth.type)) {
255                 builder.put(auth.type, auth);
256             }
257         }
258         return builder.build();
259     }
260 
261     /**
262      * Compares all fields in auth1 and auth2
263      *
264      * <p>By default {@link AuthenticatorDescription#equals(Object)} only checks the type</p>
265      */
deepEquals(AuthenticatorDescription auth1, AuthenticatorDescription auth2)266     private boolean deepEquals(AuthenticatorDescription auth1, AuthenticatorDescription auth2) {
267         return Objects.equal(auth1, auth2) &&
268                 Objects.equal(auth1.packageName, auth2.packageName) &&
269                 auth1.labelId == auth2.labelId &&
270                 auth1.iconId == auth2.iconId &&
271                 auth1.smallIconId == auth2.smallIconId &&
272                 auth1.accountPreferencesId == auth2.accountPreferencesId &&
273                 auth1.customTokens == auth2.customTokens;
274     }
275 
276 }
277