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 android.accounts.AccountManager;
20import android.accounts.AuthenticatorDescription;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.content.pm.PackageManager.NameNotFoundException;
25import android.content.pm.ResolveInfo;
26import android.content.pm.ServiceInfo;
27import android.content.res.XmlResourceParser;
28import android.util.Log;
29
30import com.android.internal.util.XmlUtils;
31import com.google.android.collect.Maps;
32
33import org.xmlpull.v1.XmlPullParser;
34import org.xmlpull.v1.XmlPullParserException;
35
36import java.io.IOException;
37import java.util.HashMap;
38import 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 */
46public 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
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     */
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     */
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     */
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
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