• 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 || mLocalAccountTypeFactory.classifyAccount(accountType)
87                 == DeviceLocalAccountTypeFactory.TYPE_SIM) {
88             AccountType type = mLocalAccountTypeFactory.getAccountType(accountType);
89             // Just in case the DeviceLocalAccountTypeFactory doesn't handle the null type
90             if (type == null) {
91                 type = new FallbackAccountType(mContext);
92             }
93             return Collections.singletonList(type);
94         }
95 
96         List<AccountType> types = mCache.get(accountType);
97         if (types == null) {
98             types = loadTypes(accountType);
99             mCache.put(accountType, types);
100         }
101         return types;
102     }
103 
hasTypeForAccount(AccountWithDataSet account)104     public boolean hasTypeForAccount(AccountWithDataSet account) {
105         return getTypeForAccount(account) != null;
106     }
107 
hasTypeWithDataset(String type, String dataSet)108     public boolean hasTypeWithDataset(String type, String dataSet) {
109         // getAccountTypes() never returns null
110         final List<AccountType> accountTypes = getAccountTypes(type);
111         for (AccountType accountType : accountTypes) {
112             if (Objects.equal(accountType.dataSet, dataSet)) {
113                 return true;
114             }
115         }
116         return false;
117     }
118 
119     /**
120      * Returns the AccountType with the matching type and dataSet or null if no account with those
121      * members exists
122      */
getType(String type, String dataSet)123     public AccountType getType(String type, String dataSet) {
124         final List<AccountType> accountTypes = getAccountTypes(type);
125         for (AccountType accountType : accountTypes) {
126             if (Objects.equal(accountType.dataSet, dataSet)) {
127                 return accountType;
128             }
129         }
130         return null;
131     }
132 
133     /**
134      * Returns the AccountType for a particular account or null if no account type exists for the
135      * account
136      */
getTypeForAccount(AccountWithDataSet account)137     public AccountType getTypeForAccount(AccountWithDataSet account) {
138         return getType(account.type, account.dataSet);
139     }
140 
shouldUpdate(AuthenticatorDescription[] auths, SyncAdapterType[] syncTypes)141     public boolean shouldUpdate(AuthenticatorDescription[] auths, SyncAdapterType[] syncTypes) {
142         Map<String, AuthenticatorDescription> contactsAuths = onlyContactSyncable(auths, syncTypes);
143         if (!contactsAuths.keySet().equals(mAuthTypes.keySet())) {
144             return true;
145         }
146         for (AuthenticatorDescription auth : contactsAuths.values()) {
147             if (!deepEquals(mAuthTypes.get(auth.type), auth)) {
148                 return true;
149             }
150         }
151         return false;
152     }
153 
supportsContactsSyncing(String accountType)154     public boolean supportsContactsSyncing(String accountType) {
155         return mAuthTypes.containsKey(accountType);
156     }
157 
loadTypes(String type)158     private List<AccountType> loadTypes(String type) {
159         final AuthenticatorDescription auth = mAuthTypes.get(type);
160         if (auth == null) {
161             if (Log.isLoggable(TAG, Log.DEBUG)) {
162                 Log.d(TAG, "Null auth type for " + type);
163             }
164             return Collections.emptyList();
165         }
166 
167         AccountType accountType;
168         if (GoogleAccountType.ACCOUNT_TYPE.equals(type)) {
169             accountType = new GoogleAccountType(mContext, auth.packageName);
170         } else if (ExchangeAccountType.isExchangeType(type)) {
171             accountType = new ExchangeAccountType(mContext, auth.packageName, type);
172         } else if (SamsungAccountType.isSamsungAccountType(mContext, type,
173                 auth.packageName)) {
174             accountType = new SamsungAccountType(mContext, auth.packageName, type);
175         } else if (!ExternalAccountType.hasContactsXml(mContext, auth.packageName)
176                 && isLocalAccountType(mLocalAccountTypeFactory, type)) {
177             if (Log.isLoggable(TAG, Log.DEBUG)) {
178                 Log.d(TAG, "Registering local account type=" + type
179                         + ", packageName=" + auth.packageName);
180             }
181             accountType = mLocalAccountTypeFactory.getAccountType(type);
182         } else {
183             if (Log.isLoggable(TAG, Log.DEBUG)) {
184                 Log.d(TAG, "Registering external account type=" + type
185                         + ", packageName=" + auth.packageName);
186             }
187             accountType = new ExternalAccountType(mContext, auth.packageName, false);
188         }
189         if (!accountType.isInitialized()) {
190             if (accountType.isEmbedded()) {
191                 throw new IllegalStateException("Problem initializing embedded type "
192                         + accountType.getClass().getCanonicalName());
193             } else {
194                 // Skip external account types that couldn't be initialized
195                 if (Log.isLoggable(TAG, Log.DEBUG)) {
196                     Log.d(TAG, "Skipping external account type=" + type
197                             + ", packageName=" + auth.packageName);
198                 }
199                 return Collections.emptyList();
200             }
201         }
202 
203         accountType.initializeFieldsFromAuthenticator(auth);
204 
205         final ImmutableList.Builder<AccountType> result = ImmutableList.builder();
206         result.add(accountType);
207 
208         for (String extensionPackage : accountType.getExtensionPackageNames()) {
209             final ExternalAccountType extensionType =
210                     new ExternalAccountType(mContext, extensionPackage, true);
211             if (!extensionType.isInitialized()) {
212                 // Skip external account types that couldn't be initialized.
213                 continue;
214             }
215             if (!extensionType.hasContactsMetadata()) {
216                 Log.w(TAG, "Skipping extension package " + extensionPackage + " because"
217                         + " it doesn't have the CONTACTS_STRUCTURE metadata");
218                 continue;
219             }
220             if (TextUtils.isEmpty(extensionType.accountType)) {
221                 Log.w(TAG, "Skipping extension package " + extensionPackage + " because"
222                         + " the CONTACTS_STRUCTURE metadata doesn't have the accountType"
223                         + " attribute");
224                 continue;
225             }
226             if (!Objects.equal(extensionType.accountType, type)) {
227                 Log.w(TAG, "Skipping extension package " + extensionPackage + " because"
228                         + " the account type + " + extensionType.accountType +
229                         " doesn't match expected type " + type);
230                 continue;
231             }
232             if (Log.isLoggable(TAG, Log.DEBUG)) {
233                 Log.d(TAG, "Registering extension package account type="
234                         + accountType.accountType + ", dataSet=" + accountType.dataSet
235                         + ", packageName=" + extensionPackage);
236             }
237 
238             result.add(extensionType);
239         }
240         return result.build();
241     }
242 
onlyContactSyncable( AuthenticatorDescription[] auths, SyncAdapterType[] syncTypes)243     private static ImmutableMap<String, AuthenticatorDescription> onlyContactSyncable(
244             AuthenticatorDescription[] auths, SyncAdapterType[] syncTypes) {
245         final Set<String> mContactSyncableTypes = new HashSet<>();
246         for (SyncAdapterType type : syncTypes) {
247             if (type.authority.equals(ContactsContract.AUTHORITY)) {
248                 mContactSyncableTypes.add(type.accountType);
249             }
250         }
251 
252         final ImmutableMap.Builder<String, AuthenticatorDescription> builder =
253                 ImmutableMap.builder();
254         for (AuthenticatorDescription auth : auths) {
255             if (mContactSyncableTypes.contains(auth.type)) {
256                 builder.put(auth.type, auth);
257             }
258         }
259         return builder.build();
260     }
261 
262     /**
263      * Compares all fields in auth1 and auth2
264      *
265      * <p>By default {@link AuthenticatorDescription#equals(Object)} only checks the type</p>
266      */
deepEquals(AuthenticatorDescription auth1, AuthenticatorDescription auth2)267     private boolean deepEquals(AuthenticatorDescription auth1, AuthenticatorDescription auth2) {
268         return Objects.equal(auth1, auth2) &&
269                 Objects.equal(auth1.packageName, auth2.packageName) &&
270                 auth1.labelId == auth2.labelId &&
271                 auth1.iconId == auth2.iconId &&
272                 auth1.smallIconId == auth2.smallIconId &&
273                 auth1.accountPreferencesId == auth2.accountPreferencesId &&
274                 auth1.customTokens == auth2.customTokens;
275     }
276 
277 }
278