Home | History | Annotate | Download | only in contacts
      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 
     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