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 ExternalSource(String resPackageName)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 inflate(Context context, int inflateLevel)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 */ inflate(Context context, XmlPullParser parser)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 getHeaderColor(Context context)188 public int getHeaderColor(Context context) { 189 return 0xff6d86b4; 190 } 191 192 @Override getSideBarColor(Context context)193 public int getSideBarColor(Context context) { 194 return 0xff6d86b4; 195 } 196 } 197