Home | History | Annotate | Download | only in model
      1 /*
      2  * Copyright (C) 2009 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.loaderapp.model;
     18 
     19 import org.xmlpull.v1.XmlPullParser;
     20 import org.xmlpull.v1.XmlPullParserException;
     21 
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.ResolveInfo;
     26 import android.content.res.TypedArray;
     27 import android.content.res.XmlResourceParser;
     28 import android.util.AttributeSet;
     29 import android.util.Xml;
     30 
     31 import java.io.IOException;
     32 import java.util.List;
     33 
     34 /*
     35 
     36 <!-- example of what SourceConstraints would look like in XML -->
     37 <!-- NOTE: may not directly match the current structure version -->
     38 
     39 <DataKind
     40     mimeType="vnd.android.cursor.item/email"
     41     title="@string/title_postal"
     42     icon="@drawable/icon_postal"
     43     weight="12"
     44     editable="true">
     45 
     46     <!-- these are defined using string-builder-ish -->
     47     <ActionHeader></ActionHeader>
     48     <ActionBody socialSummary="true" />  <!-- can pull together various columns -->
     49 
     50     <!-- ordering handles precedence the "insert/add" case -->
     51     <!-- assume uniform type when missing "column", use title in place -->
     52     <EditTypes column="data5" overallMax="-1">
     53         <EditType rawValue="0" label="@string/type_home" specificMax="-1" />
     54         <EditType rawValue="1" label="@string/type_work" specificMax="-1" secondary="true" />
     55         <EditType rawValue="4" label="@string/type_custom" customColumn="data6" specificMax="-1" secondary="true" />
     56     </EditTypes>
     57 
     58     <!-- when single edit field, simplifies edit case -->
     59     <EditField column="data1" title="@string/field_family_name" android:inputType="textCapWords|textPhonetic" />
     60     <EditField column="data2" title="@string/field_given_name" android:minLines="2" />
     61     <EditField column="data3" title="@string/field_suffix" />
     62 
     63 </DataKind>
     64 
     65 */
     66 
     67 /**
     68  * Internal structure that represents constraints and styles for a specific data
     69  * source, such as the various data types they support, including details on how
     70  * those types should be rendered and edited.
     71  * <p>
     72  * In the future this may be inflated from XML defined by a data source.
     73  */
     74 public class ExternalSource extends FallbackSource {
     75     private static final String ACTION_SYNC_ADAPTER = "android.content.SyncAdapter";
     76     private static final String METADATA_CONTACTS = "android.provider.CONTACTS_STRUCTURE";
     77 
     78     private interface InflateTags {
     79         final String CONTACTS_SOURCE = "ContactsSource";
     80         final String CONTACTS_DATA_KIND = "ContactsDataKind";
     81     }
     82 
     83     public ExternalSource(String resPackageName) {
     84         this.resPackageName = resPackageName;
     85         this.summaryResPackageName = resPackageName;
     86     }
     87 
     88     /**
     89      * Ensure that the constraint rules behind this {@link ContactsSource} have
     90      * been inflated. Because this may involve parsing meta-data from
     91      * {@link PackageManager}, it shouldn't be called from a UI thread.
     92      */
     93     @Override
     94     public void inflate(Context context, int inflateLevel) {
     95         // Handle unknown sources by searching their package
     96         final PackageManager pm = context.getPackageManager();
     97         final Intent syncAdapter = new Intent(ACTION_SYNC_ADAPTER);
     98         final List<ResolveInfo> matches = pm.queryIntentServices(syncAdapter,
     99                 PackageManager.GET_META_DATA);
    100         for (ResolveInfo info : matches) {
    101             final XmlResourceParser parser = info.serviceInfo.loadXmlMetaData(pm,
    102                     METADATA_CONTACTS);
    103             if (parser == null) continue;
    104             inflate(context, parser);
    105         }
    106 
    107         // Bring in name and photo from fallback source, which are non-optional
    108         inflateStructuredName(context, inflateLevel);
    109         inflatePhoto(context, inflateLevel);
    110 
    111         setInflatedLevel(inflateLevel);
    112     }
    113 
    114     /**
    115      * Inflate this {@link ContactsSource} from the given parser. This may only
    116      * load details matching the publicly-defined schema.
    117      */
    118     protected void inflate(Context context, XmlPullParser parser) {
    119         final AttributeSet attrs = Xml.asAttributeSet(parser);
    120 
    121         try {
    122             int type;
    123             while ((type = parser.next()) != XmlPullParser.START_TAG
    124                     && type != XmlPullParser.END_DOCUMENT) {
    125                 // Drain comments and whitespace
    126             }
    127 
    128             if (type != XmlPullParser.START_TAG) {
    129                 throw new IllegalStateException("No start tag found");
    130             }
    131 
    132             if (!InflateTags.CONTACTS_SOURCE.equals(parser.getName())) {
    133                 throw new IllegalStateException("Top level element must be "
    134                         + InflateTags.CONTACTS_SOURCE);
    135             }
    136 
    137             // Parse all children kinds
    138             final int depth = parser.getDepth();
    139             while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
    140                     && type != XmlPullParser.END_DOCUMENT) {
    141                 if (type == XmlPullParser.END_TAG
    142                         || !InflateTags.CONTACTS_DATA_KIND.equals(parser.getName())) {
    143                     continue;
    144                 }
    145 
    146                 final TypedArray a = context.obtainStyledAttributes(attrs,
    147                         android.R.styleable.ContactsDataKind);
    148                 final DataKind kind = new DataKind();
    149 
    150                 kind.mimeType = a
    151                         .getString(com.android.internal.R.styleable.ContactsDataKind_mimeType);
    152                 kind.iconRes = a.getResourceId(
    153                         com.android.internal.R.styleable.ContactsDataKind_icon, -1);
    154 
    155                 final String summaryColumn = a
    156                         .getString(com.android.internal.R.styleable.ContactsDataKind_summaryColumn);
    157                 if (summaryColumn != null) {
    158                     // Inflate a specific column as summary when requested
    159                     kind.actionHeader = new FallbackSource.SimpleInflater(summaryColumn);
    160                 }
    161 
    162                 final String detailColumn = a
    163                         .getString(com.android.internal.R.styleable.ContactsDataKind_detailColumn);
    164                 final boolean detailSocialSummary = a.getBoolean(
    165                         com.android.internal.R.styleable.ContactsDataKind_detailSocialSummary,
    166                         false);
    167 
    168                 if (detailSocialSummary) {
    169                     // Inflate social summary when requested
    170                     kind.actionBodySocial = true;
    171                 }
    172 
    173                 if (detailColumn != null) {
    174                     // Inflate specific column as summary
    175                     kind.actionBody = new FallbackSource.SimpleInflater(detailColumn);
    176                 }
    177 
    178                 addKind(kind);
    179             }
    180         } catch (XmlPullParserException e) {
    181             throw new IllegalStateException("Problem reading XML", e);
    182         } catch (IOException e) {
    183             throw new IllegalStateException("Problem reading XML", e);
    184         }
    185     }
    186 
    187     @Override
    188     public int getHeaderColor(Context context) {
    189         return 0xff6d86b4;
    190     }
    191 
    192     @Override
    193     public int getSideBarColor(Context context) {
    194         return 0xff6d86b4;
    195     }
    196 }
    197