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>&lt;Picture android:priority="6"/&gt;</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