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