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 17package com.android.providers.contacts; 18 19import com.android.internal.util.XmlUtils; 20import com.google.android.collect.Maps; 21 22import org.xmlpull.v1.XmlPullParser; 23import org.xmlpull.v1.XmlPullParserException; 24 25import android.accounts.AccountManager; 26import android.accounts.AuthenticatorDescription; 27import android.content.Context; 28import android.content.pm.PackageInfo; 29import android.content.pm.PackageManager; 30import android.content.pm.ServiceInfo; 31import android.content.pm.PackageManager.NameNotFoundException; 32import android.content.res.XmlResourceParser; 33import android.util.Log; 34 35import java.io.IOException; 36import java.util.HashMap; 37 38/** 39 * Maintains a cache of photo priority per account type. During contact aggregation 40 * photo with a higher priority is chosen for the the entire contact, barring an 41 * explicit override by the user, which is captured as the is_superprimary flag 42 * on the photo itself. 43 */ 44public class PhotoPriorityResolver { 45 private static final String TAG = "PhotoPriorityResolver"; 46 47 public static final int DEFAULT_PRIORITY = 7; 48 49 /** 50 * Meta-data key for the contacts configuration associated with a sync service. 51 */ 52 private static final String METADATA_CONTACTS = "android.provider.CONTACTS_STRUCTURE"; 53 54 /** 55 * The XML tag capturing the picture priority. The syntax is: 56 * <code><Picture android:priority="6"/></code> 57 */ 58 private static final String PICTURE_TAG = "Picture"; 59 60 /** 61 * Name of the attribute of the Picture tag capturing the priority itself. 62 */ 63 private static final String PRIORITY_ATTR = "priority"; 64 65 private Context mContext; 66 private HashMap<String, Integer> mPhotoPriorities = Maps.newHashMap(); 67 68 public PhotoPriorityResolver(Context context) { 69 mContext = context; 70 } 71 72 /** 73 * Returns the photo priority for the specified account type. Maintains cache 74 * of photo priorities. 75 */ 76 public synchronized int getPhotoPriority(String accountType) { 77 if (accountType == null) { 78 return DEFAULT_PRIORITY; 79 } 80 81 Integer priority = mPhotoPriorities.get(accountType); 82 if (priority == null) { 83 priority = resolvePhotoPriority(accountType); 84 mPhotoPriorities.put(accountType, priority); 85 } 86 return priority; 87 } 88 89 /** 90 * Finds photo priority for the specified account type. 91 */ 92 private int resolvePhotoPriority(String accountType) { 93 final AccountManager am = AccountManager.get(mContext); 94 95 for (AuthenticatorDescription auth : am.getAuthenticatorTypes()) { 96 if (accountType.equals(auth.type)) { 97 return resolvePhotoPriorityFromMetaData(auth.packageName); 98 } 99 } 100 101 return DEFAULT_PRIORITY; 102 } 103 104 /** 105 * Finds the meta-data XML containing the contacts configuration and 106 * reads the picture priority from that file. 107 */ 108 /* package */ int resolvePhotoPriorityFromMetaData(String packageName) { 109 final PackageManager pm = mContext.getPackageManager(); 110 try { 111 PackageInfo pi = pm.getPackageInfo(packageName, PackageManager.GET_SERVICES 112 | PackageManager.GET_META_DATA); 113 if (pi != null && pi.services != null) { 114 for (ServiceInfo si : pi.services) { 115 final XmlResourceParser parser = si.loadXmlMetaData(pm, METADATA_CONTACTS); 116 if (parser != null) { 117 return loadPhotoPriorityFromXml(mContext, parser); 118 } 119 } 120 } 121 } catch (NameNotFoundException e) { 122 Log.w(TAG, "Problem loading photo priorities: " + e.toString()); 123 } 124 return DEFAULT_PRIORITY; 125 } 126 127 private int loadPhotoPriorityFromXml(Context context, XmlPullParser parser) { 128 int priority = DEFAULT_PRIORITY; 129 try { 130 int type; 131 while ((type = parser.next()) != XmlPullParser.START_TAG 132 && type != XmlPullParser.END_DOCUMENT) { 133 // Drain comments and whitespace 134 } 135 136 if (type != XmlPullParser.START_TAG) { 137 throw new IllegalStateException("No start tag found"); 138 } 139 140 final int depth = parser.getDepth(); 141 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 142 && type != XmlPullParser.END_DOCUMENT) { 143 String name = parser.getName(); 144 if (type == XmlPullParser.START_TAG && PICTURE_TAG.equals(name)) { 145 int attributeCount = parser.getAttributeCount(); 146 for (int i = 0; i < attributeCount; i++) { 147 String attr = parser.getAttributeName(i); 148 if (PRIORITY_ATTR.equals(attr)) { 149 priority = XmlUtils.convertValueToInt(parser.getAttributeValue(i), 150 DEFAULT_PRIORITY); 151 } else { 152 throw new IllegalStateException("Unsupported attribute " + attr); 153 } 154 } 155 } 156 } 157 } catch (XmlPullParserException e) { 158 throw new IllegalStateException("Problem reading XML", e); 159 } catch (IOException e) { 160 throw new IllegalStateException("Problem reading XML", e); 161 } 162 163 return priority; 164 } 165} 166