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