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