1 /* 2 * Copyright (C) 2010 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.providers.contacts; 18 19 import android.accounts.AccountManager; 20 import android.accounts.AuthenticatorDescription; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.PackageManager; 24 import android.content.pm.PackageManager.NameNotFoundException; 25 import android.content.pm.ResolveInfo; 26 import android.content.pm.ServiceInfo; 27 import android.content.res.XmlResourceParser; 28 import android.util.Log; 29 30 import com.android.internal.util.XmlUtils; 31 import com.google.android.collect.Maps; 32 33 import org.xmlpull.v1.XmlPullParser; 34 import org.xmlpull.v1.XmlPullParserException; 35 36 import java.io.IOException; 37 import java.util.HashMap; 38 import java.util.List; 39 40 /** 41 * Maintains a cache of photo priority per account type. During contact aggregation 42 * photo with a higher priority is chosen for the the entire contact, barring an 43 * explicit override by the user, which is captured as the is_superprimary flag 44 * on the photo itself. 45 */ 46 public class PhotoPriorityResolver { 47 private static final String TAG = "PhotoPriorityResolver"; 48 49 public static final int DEFAULT_PRIORITY = 7; 50 51 private static final String SYNC_META_DATA = "android.content.SyncAdapter"; 52 53 /** 54 * The metadata name for so-called "contacts.xml". 55 * 56 * On LMP and later, we also accept the "alternate" name. 57 * This is to allow sync adapters to have a contacts.xml without making it visible on older 58 * platforms. If you modify this also update the matching list in 59 * ContactsCommon/ExternalAccountType. 60 */ 61 private static final String[] METADATA_CONTACTS_NAMES = new String[] { 62 "android.provider.ALTERNATE_CONTACTS_STRUCTURE", 63 "android.provider.CONTACTS_STRUCTURE" 64 }; 65 66 67 /** 68 * The XML tag capturing the picture priority. The syntax is: 69 * <code><Picture android:priority="6"/></code> 70 */ 71 private static final String PICTURE_TAG = "Picture"; 72 73 /** 74 * Name of the attribute of the Picture tag capturing the priority itself. 75 */ 76 private static final String PRIORITY_ATTR = "priority"; 77 78 private Context mContext; 79 private HashMap<String, Integer> mPhotoPriorities = Maps.newHashMap(); 80 PhotoPriorityResolver(Context context)81 public PhotoPriorityResolver(Context context) { 82 mContext = context; 83 } 84 85 /** 86 * Returns the photo priority for the specified account type. Maintains cache 87 * of photo priorities. 88 */ getPhotoPriority(String accountType)89 public synchronized int getPhotoPriority(String accountType) { 90 if (accountType == null) { 91 return DEFAULT_PRIORITY; 92 } 93 94 Integer priority = mPhotoPriorities.get(accountType); 95 if (priority == null) { 96 priority = resolvePhotoPriority(accountType); 97 mPhotoPriorities.put(accountType, priority); 98 } 99 return priority; 100 } 101 102 /** 103 * Finds photo priority for the specified account type. 104 */ resolvePhotoPriority(String accountType)105 private int resolvePhotoPriority(String accountType) { 106 final AccountManager am = AccountManager.get(mContext); 107 108 for (AuthenticatorDescription auth : am.getAuthenticatorTypes()) { 109 if (accountType.equals(auth.type)) { 110 return resolvePhotoPriorityFromMetaData(auth.packageName); 111 } 112 } 113 114 return DEFAULT_PRIORITY; 115 } 116 117 /** 118 * Finds the meta-data XML containing the contacts configuration and 119 * reads the picture priority from that file. 120 */ resolvePhotoPriorityFromMetaData(String packageName)121 /* package */ int resolvePhotoPriorityFromMetaData(String packageName) { 122 final PackageManager pm = mContext.getPackageManager(); 123 final Intent intent = new Intent(SYNC_META_DATA).setPackage(packageName); 124 final List<ResolveInfo> intentServices = pm.queryIntentServices(intent, 125 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); 126 127 if (intentServices != null) { 128 for (final ResolveInfo resolveInfo : intentServices) { 129 final ServiceInfo serviceInfo = resolveInfo.serviceInfo; 130 if (serviceInfo == null) { 131 continue; 132 } 133 for (String metadataName : METADATA_CONTACTS_NAMES) { 134 final XmlResourceParser parser = serviceInfo.loadXmlMetaData( 135 pm, metadataName); 136 if (parser != null) { 137 return loadPhotoPriorityFromXml(mContext, parser); 138 } 139 } 140 } 141 } 142 return DEFAULT_PRIORITY; 143 } 144 loadPhotoPriorityFromXml(Context context, XmlPullParser parser)145 private int loadPhotoPriorityFromXml(Context context, XmlPullParser parser) { 146 int priority = DEFAULT_PRIORITY; 147 try { 148 int type; 149 while ((type = parser.next()) != XmlPullParser.START_TAG 150 && type != XmlPullParser.END_DOCUMENT) { 151 // Drain comments and whitespace 152 } 153 154 if (type != XmlPullParser.START_TAG) { 155 throw new IllegalStateException("No start tag found"); 156 } 157 158 final int depth = parser.getDepth(); 159 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 160 && type != XmlPullParser.END_DOCUMENT) { 161 String name = parser.getName(); 162 if (type == XmlPullParser.START_TAG && PICTURE_TAG.equals(name)) { 163 int attributeCount = parser.getAttributeCount(); 164 for (int i = 0; i < attributeCount; i++) { 165 String attr = parser.getAttributeName(i); 166 if (PRIORITY_ATTR.equals(attr)) { 167 priority = XmlUtils.convertValueToInt(parser.getAttributeValue(i), 168 DEFAULT_PRIORITY); 169 } else { 170 throw new IllegalStateException("Unsupported attribute " + attr); 171 } 172 } 173 } 174 } 175 } catch (XmlPullParserException e) { 176 throw new IllegalStateException("Problem reading XML", e); 177 } catch (IOException e) { 178 throw new IllegalStateException("Problem reading XML", e); 179 } 180 181 return priority; 182 } 183 } 184