VCardRecord.java revision 8833d906e3c2d0d4e1b339d6c8fdd87cc23640c8
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.apps.tag.record; 18 19import com.android.apps.tag.R; 20import com.google.common.base.Preconditions; 21 22import android.app.Activity; 23import android.content.ActivityNotFoundException; 24import android.content.ContentUris; 25import android.content.Context; 26import android.content.Intent; 27import android.content.pm.PackageManager; 28import android.content.pm.ResolveInfo; 29import android.content.res.AssetFileDescriptor; 30import android.database.Cursor; 31import android.graphics.drawable.Drawable; 32import android.net.Uri; 33import android.nfc.NdefRecord; 34import android.os.AsyncTask; 35import android.os.Parcel; 36import android.os.Parcelable; 37import android.provider.ContactsContract; 38import android.util.Log; 39import android.view.LayoutInflater; 40import android.view.View; 41import android.view.View.OnClickListener; 42import android.view.ViewGroup; 43import android.widget.ImageView; 44import android.widget.TextView; 45 46import java.io.FileInputStream; 47import java.io.FileNotFoundException; 48import java.io.IOException; 49import java.io.InputStream; 50import java.lang.ref.WeakReference; 51import java.util.List; 52 53/** 54 * VCard Ndef Record object 55 */ 56public class VCardRecord extends ParsedNdefRecord implements OnClickListener { 57 private static final String TAG = VCardRecord.class.getSimpleName(); 58 59 public static final String RECORD_TYPE = "vcard"; 60 61 private final byte[] mVCard; 62 63 private VCardRecord(byte[] content) { 64 mVCard = content; 65 } 66 67 @Override 68 public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) { 69 70 Uri uri = activity.getIntent().getData(); 71 uri = Uri.withAppendedPath(uri, Integer.toString(offset)); 72 uri = Uri.withAppendedPath(uri, "mime"); 73 74 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 75 return RecordUtils.getViewsForIntent(activity, inflater, parent, this, intent, 76 activity.getString(R.string.import_vcard)); 77 } 78 79 /** 80 * Returns a view in a list of record types for adding new records to a message. 81 */ 82 public static View getAddView(Context context, LayoutInflater inflater, ViewGroup parent) { 83 ViewGroup root = (ViewGroup) inflater.inflate( 84 R.layout.tag_add_record_list_item, parent, false); 85 86 Intent intent = new Intent(Intent.ACTION_PICK); 87 intent.setType(ContactsContract.Contacts.CONTENT_TYPE); 88 89 PackageManager pm = context.getPackageManager(); 90 List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0); 91 if (activities.isEmpty()) { 92 return null; 93 } 94 95 ResolveInfo info = activities.get(0); 96 ((ImageView) root.findViewById(R.id.image)).setImageDrawable(info.loadIcon(pm)); 97 ((TextView) root.findViewById(R.id.text)).setText(context.getString(R.string.contact)); 98 99 root.setTag(new VCardRecordEditInfo(intent)); 100 return root; 101 } 102 103 public static VCardRecord parse(NdefRecord record) { 104 MimeRecord underlyingRecord = MimeRecord.parse(record); 105 106 // TODO: Add support for other vcard mime types. 107 Preconditions.checkArgument("text/x-vCard".equals(underlyingRecord.getMimeType())); 108 return new VCardRecord(underlyingRecord.getContent()); 109 } 110 111 public static NdefRecord newVCardRecord(byte[] data) { 112 return MimeRecord.newMimeRecord("text/x-vCard", data); 113 } 114 115 @Override 116 public void onClick(View view) { 117 RecordUtils.ClickInfo info = (RecordUtils.ClickInfo) view.getTag(); 118 try { 119 info.activity.startActivity(info.intent); 120 info.activity.finish(); 121 } catch (ActivityNotFoundException e) { 122 // The activity wansn't found for some reason. Don't crash, but don't do anything. 123 Log.e(TAG, "Failed to launch activity for intent " + info.intent, e); 124 } 125 } 126 127 public static boolean isVCard(NdefRecord record) { 128 try { 129 parse(record); 130 return true; 131 } catch (IllegalArgumentException e) { 132 return false; 133 } 134 } 135 136 private static class VCardRecordEditInfo extends RecordEditInfo { 137 private final Intent mIntent; 138 private Uri mLookupUri; 139 140 private WeakReference<View> mActiveView = null; 141 142 private String mCachedName = null; 143 private Drawable mCachedPhoto = null; 144 private byte[] mCachedValue = null; 145 146 private static final class CacheData { 147 public String mName; 148 public Drawable mPhoto; 149 public byte[] mVcard; 150 } 151 152 public VCardRecordEditInfo(Intent intent) { 153 super(RECORD_TYPE); 154 mIntent = intent; 155 } 156 157 protected VCardRecordEditInfo(Parcel parcel) { 158 super(parcel); 159 mIntent = parcel.readParcelable(null); 160 mLookupUri = parcel.readParcelable(null); 161 } 162 163 @Override 164 public Intent getPickIntent() { 165 return mIntent; 166 } 167 168 private void fetchValues(final Context context) { 169 if (mCachedValue != null) { 170 bindView(); 171 return; 172 } 173 174 new AsyncTask<Uri, Void, CacheData>() { 175 @Override 176 protected CacheData doInBackground(Uri... params) { 177 Cursor cursor = null; 178 long id; 179 String lookupKey = null; 180 Uri lookupUri = params[0]; 181 CacheData result = new CacheData(); 182 try { 183 String[] projection = { 184 ContactsContract.Contacts._ID, 185 ContactsContract.Contacts.LOOKUP_KEY, 186 ContactsContract.Contacts.DISPLAY_NAME 187 }; 188 cursor = context.getContentResolver().query( 189 lookupUri, projection, null, null, null); 190 cursor.moveToFirst(); 191 id = cursor.getLong(0); 192 lookupKey = cursor.getString(1); 193 result.mName = cursor.getString(2); 194 195 } finally { 196 if (cursor != null) { 197 cursor.close(); 198 cursor = null; 199 } 200 } 201 202 if (lookupKey == null) { 203 // TODO: handle errors. 204 return null; 205 } 206 207 // Note: the lookup key should already encoded. 208 Uri vcardUri = Uri.withAppendedPath( 209 ContactsContract.Contacts.CONTENT_VCARD_URI, 210 lookupKey); 211 212 AssetFileDescriptor descriptor; 213 FileInputStream in = null; 214 try { 215 descriptor = context.getContentResolver().openAssetFileDescriptor( 216 vcardUri, "r"); 217 result.mVcard = new byte[(int) descriptor.getLength()]; 218 219 in = descriptor.createInputStream(); 220 in.read(result.mVcard); 221 in.close(); 222 } catch (FileNotFoundException e) { 223 return null; 224 } catch (IOException e) { 225 return null; 226 } 227 228 Uri contactUri = ContentUris.withAppendedId( 229 ContactsContract.Contacts.CONTENT_URI, id); 230 InputStream photoIn = ContactsContract.Contacts.openContactPhotoInputStream( 231 context.getContentResolver(), contactUri); 232 if (photoIn != null) { 233 result.mPhoto = Drawable.createFromStream(photoIn, contactUri.toString()); 234 } 235 return result; 236 } 237 238 @Override 239 protected void onPostExecute(CacheData data) { 240 if (data == null) { 241 return; 242 } 243 244 mCachedName = data.mName; 245 mCachedValue = data.mVcard; 246 mCachedPhoto = data.mPhoto; 247 bindView(); 248 } 249 }.execute(mLookupUri); 250 } 251 252 @Override 253 public NdefRecord getValue() { 254 return (mCachedValue == null) ? null : VCardRecord.newVCardRecord(mCachedValue); 255 } 256 257 @Override 258 public void handlePickResult(Context context, Intent data) { 259 mLookupUri = data.getData(); 260 mCachedValue = null; 261 mCachedName = null; 262 mCachedPhoto = null; 263 } 264 265 private void bindView() { 266 View view = (mActiveView == null) ? null : mActiveView.get(); 267 if (view == null) { 268 return; 269 } 270 271 if (mCachedPhoto != null) { 272 ((ImageView) view.findViewById(R.id.photo)).setImageDrawable(mCachedPhoto); 273 } 274 275 if (mCachedName != null) { 276 ((TextView) view.findViewById(R.id.display_name)).setText(mCachedName); 277 } 278 } 279 280 @Override 281 public View getEditView( 282 Activity activity, LayoutInflater inflater, 283 ViewGroup parent, EditCallbacks callbacks) { 284 View result = buildEditView( 285 activity, inflater, R.layout.tag_edit_vcard, parent, callbacks); 286 287 mActiveView = new WeakReference<View>(result); 288 result.setOnClickListener(this); 289 290 // Show default contact photo until the data loads. 291 ((ImageView) result.findViewById(R.id.photo)).setImageDrawable( 292 activity.getResources().getDrawable(R.drawable.default_contact_photo)); 293 294 fetchValues(activity); 295 return result; 296 } 297 298 @Override 299 public void writeToParcel(Parcel out, int flags) { 300 super.writeToParcel(out, flags); 301 out.writeParcelable(mIntent, flags); 302 out.writeParcelable(mLookupUri, flags); 303 } 304 305 @SuppressWarnings("unused") 306 public static final Parcelable.Creator<VCardRecordEditInfo> CREATOR = 307 new Parcelable.Creator<VCardRecordEditInfo>() { 308 @Override 309 public VCardRecordEditInfo createFromParcel(Parcel in) { 310 return new VCardRecordEditInfo(in); 311 } 312 313 @Override 314 public VCardRecordEditInfo[] newArray(int size) { 315 return new VCardRecordEditInfo[size]; 316 } 317 }; 318 319 @Override 320 public int describeContents() { 321 return 0; 322 } 323 324 @Override 325 public void onClick(View target) { 326 if (this == target.getTag()) { 327 mCallbacks.startPickForRecord(this, mIntent); 328 } else { 329 super.onClick(target); 330 } 331 } 332 } 333} 334