/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.providers.contacts; import android.accounts.AccountManager; import android.accounts.AuthenticatorDescription; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.XmlResourceParser; import android.util.Log; import com.android.internal.util.XmlUtils; import com.google.android.collect.Maps; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.HashMap; import java.util.List; /** * Maintains a cache of photo priority per account type. During contact aggregation * photo with a higher priority is chosen for the the entire contact, barring an * explicit override by the user, which is captured as the is_superprimary flag * on the photo itself. */ public class PhotoPriorityResolver { private static final String TAG = "PhotoPriorityResolver"; public static final int DEFAULT_PRIORITY = 7; private static final String SYNC_META_DATA = "android.content.SyncAdapter"; /** * The metadata name for so-called "contacts.xml". * * On LMP and later, we also accept the "alternate" name. * This is to allow sync adapters to have a contacts.xml without making it visible on older * platforms. If you modify this also update the matching list in * ContactsCommon/ExternalAccountType. */ private static final String[] METADATA_CONTACTS_NAMES = new String[] { "android.provider.ALTERNATE_CONTACTS_STRUCTURE", "android.provider.CONTACTS_STRUCTURE" }; /** * The XML tag capturing the picture priority. The syntax is: * <Picture android:priority="6"/> */ private static final String PICTURE_TAG = "Picture"; /** * Name of the attribute of the Picture tag capturing the priority itself. */ private static final String PRIORITY_ATTR = "priority"; private Context mContext; private HashMap mPhotoPriorities = Maps.newHashMap(); public PhotoPriorityResolver(Context context) { mContext = context; } /** * Returns the photo priority for the specified account type. Maintains cache * of photo priorities. */ public synchronized int getPhotoPriority(String accountType) { if (accountType == null) { return DEFAULT_PRIORITY; } Integer priority = mPhotoPriorities.get(accountType); if (priority == null) { priority = resolvePhotoPriority(accountType); mPhotoPriorities.put(accountType, priority); } return priority; } /** * Finds photo priority for the specified account type. */ private int resolvePhotoPriority(String accountType) { final AccountManager am = AccountManager.get(mContext); for (AuthenticatorDescription auth : am.getAuthenticatorTypes()) { if (accountType.equals(auth.type)) { return resolvePhotoPriorityFromMetaData(auth.packageName); } } return DEFAULT_PRIORITY; } /** * Finds the meta-data XML containing the contacts configuration and * reads the picture priority from that file. */ /* package */ int resolvePhotoPriorityFromMetaData(String packageName) { final PackageManager pm = mContext.getPackageManager(); final Intent intent = new Intent(SYNC_META_DATA).setPackage(packageName); final List intentServices = pm.queryIntentServices(intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); if (intentServices != null) { for (final ResolveInfo resolveInfo : intentServices) { final ServiceInfo serviceInfo = resolveInfo.serviceInfo; if (serviceInfo == null) { continue; } for (String metadataName : METADATA_CONTACTS_NAMES) { final XmlResourceParser parser = serviceInfo.loadXmlMetaData( pm, metadataName); if (parser != null) { return loadPhotoPriorityFromXml(mContext, parser); } } } } return DEFAULT_PRIORITY; } private int loadPhotoPriorityFromXml(Context context, XmlPullParser parser) { int priority = DEFAULT_PRIORITY; try { int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Drain comments and whitespace } if (type != XmlPullParser.START_TAG) { throw new IllegalStateException("No start tag found"); } final int depth = parser.getDepth(); while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { String name = parser.getName(); if (type == XmlPullParser.START_TAG && PICTURE_TAG.equals(name)) { int attributeCount = parser.getAttributeCount(); for (int i = 0; i < attributeCount; i++) { String attr = parser.getAttributeName(i); if (PRIORITY_ATTR.equals(attr)) { priority = XmlUtils.convertValueToInt(parser.getAttributeValue(i), DEFAULT_PRIORITY); } else { throw new IllegalStateException("Unsupported attribute " + attr); } } } } } catch (XmlPullParserException e) { throw new IllegalStateException("Problem reading XML", e); } catch (IOException e) { throw new IllegalStateException("Problem reading XML", e); } return priority; } }