Restore tag records when re-starting MyTag activity.
[android/platform/packages/apps/Tag.git] / src / com / android / apps / tag / record / ImageRecord.java
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.apps.tag.record;
18
19 import com.android.apps.tag.R;
20 import com.google.common.base.Preconditions;
21
22 import android.app.Activity;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.database.Cursor;
28 import android.graphics.Bitmap;
29 import android.graphics.BitmapFactory;
30 import android.media.ThumbnailUtils;
31 import android.nfc.NdefRecord;
32 import android.os.Parcel;
33 import android.os.Parcelable;
34 import android.provider.MediaStore;
35 import android.provider.OpenableColumns;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import android.widget.ImageView;
40 import android.widget.TextView;
41 import android.widget.Toast;
42
43 import java.io.ByteArrayOutputStream;
44 import java.util.List;
45
46 /**
47  * A NdefRecord corresponding to an image type.
48  */
49 public class ImageRecord extends ParsedNdefRecord {
50
51     public static final String RECORD_TYPE = "ImageRecord";
52
53     private final Bitmap mBitmap;
54
55     private ImageRecord(Bitmap bitmap) {
56         mBitmap = Preconditions.checkNotNull(bitmap);
57     }
58
59     @Override
60     public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) {
61         ImageView image = (ImageView) inflater.inflate(R.layout.tag_image, parent, false);
62         image.setImageBitmap(mBitmap);
63         return image;
64     }
65
66     @Override
67     public RecordEditInfo getEditInfo(Activity host) {
68         return new ImageRecordEditInfo(mBitmap);
69     }
70
71     private static Intent getPickImageIntent() {
72         Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
73         intent.addCategory(Intent.CATEGORY_OPENABLE);
74         intent.setType("image/*");
75         return intent;
76     }
77
78     /**
79      * Returns a view in a list of record types for adding new records to a message.
80      */
81     public static View getAddView(Context context, LayoutInflater inflater, ViewGroup parent) {
82         ViewGroup root = (ViewGroup) inflater.inflate(
83                 R.layout.tag_add_record_list_item, parent, false);
84
85         // Determine which Activity can retrieve images.
86         Intent intent = getPickImageIntent();
87         PackageManager pm = context.getPackageManager();
88         List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0);
89         if (activities.isEmpty()) {
90             return null;
91         }
92
93         ResolveInfo info = activities.get(0);
94         ((ImageView) root.findViewById(R.id.image)).setImageDrawable(info.loadIcon(pm));
95         ((TextView) root.findViewById(R.id.text)).setText(context.getString(R.string.photo));
96
97         root.setTag(new ImageRecordEditInfo());
98         return root;
99     }
100
101     public static ImageRecord parse(NdefRecord record) {
102         MimeRecord underlyingRecord = MimeRecord.parse(record);
103         Preconditions.checkArgument(underlyingRecord.getMimeType().startsWith("image/"));
104
105         // Try to ensure it's a legal, valid image
106         byte[] content = underlyingRecord.getContent();
107         Bitmap bitmap = BitmapFactory.decodeByteArray(content, 0, content.length);
108         if (bitmap == null) {
109             throw new IllegalArgumentException("not a valid image file");
110         }
111         return new ImageRecord(bitmap);
112     }
113
114     public static boolean isImage(NdefRecord record) {
115         try {
116             parse(record);
117             return true;
118         } catch (IllegalArgumentException e) {
119             return false;
120         }
121     }
122
123     public static NdefRecord newImageRecord(Bitmap bitmap) {
124         ByteArrayOutputStream out = new ByteArrayOutputStream();
125         bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
126         byte[] content = out.toByteArray();
127         return MimeRecord.newMimeRecord("image/jpeg", content);
128     }
129
130     private static class ImageRecordEditInfo extends RecordEditInfo {
131         /**
132          * The path on the device where we can load the image. If this is set, the value will be
133          * lazily loaded from the path.
134          */
135         private String mCurrentPath;
136
137         /**
138          * The actual current value of the image for the record.
139          */
140         private Bitmap mCachedValue;
141
142         /**
143          * Pixel size to crop loaded images to.
144          */
145         public static final int MAX_IMAGE_SIZE = 128;
146
147         public ImageRecordEditInfo() {
148             super(RECORD_TYPE);
149             mCurrentPath = "";
150             mCachedValue = null;
151         }
152
153         public ImageRecordEditInfo(String path) {
154             super(RECORD_TYPE);
155             mCurrentPath = path;
156             mCachedValue = null;
157         }
158
159         public ImageRecordEditInfo(Bitmap value) {
160             super(RECORD_TYPE);
161             mCurrentPath = "";
162             mCachedValue = value;
163         }
164
165         protected ImageRecordEditInfo(Parcel parcel) {
166             super(parcel);
167             mCurrentPath = parcel.readString();
168             mCachedValue = parcel.readParcelable(null);
169         }
170
171         @Override
172         public Intent getPickIntent() {
173             return getPickImageIntent();
174         }
175
176         @Override
177         public NdefRecord getValue() {
178             return ImageRecord.newImageRecord(getValueInternal());
179         }
180
181         private Bitmap getValueInternal() {
182             if (mCachedValue == null) {
183                 Bitmap original = BitmapFactory.decodeFile(mCurrentPath);
184                 int width = original.getWidth();
185                 int height = original.getHeight();
186                 int major = (width > height) ? width : height;
187                 if (major > MAX_IMAGE_SIZE) {
188                     double scale = 1.0 * MAX_IMAGE_SIZE / major;
189                     width *= scale;
190                     height *= scale;
191                 }
192                 mCachedValue = ThumbnailUtils.extractThumbnail(original, width, height);
193             }
194             return mCachedValue;
195         }
196
197         @Override
198         public void handlePickResult(Context context, Intent data) {
199             Cursor cursor = null;
200             mCachedValue = null;
201
202             try {
203                 String[] projection = { MediaStore.Images.Media.DATA, OpenableColumns.SIZE };
204                 cursor = context.getContentResolver().query(
205                         data.getData(), projection, null, null, null);
206
207                 if (cursor == null) {
208                     Toast.makeText(
209                             context,
210                             context.getResources().getString(R.string.bad_photo),
211                             Toast.LENGTH_LONG).show();
212                     throw new IllegalArgumentException("Selected image could not be loaded");
213                 }
214
215                 cursor.moveToFirst();
216                 int size = cursor.getInt(1);
217                 mCurrentPath = cursor.getString(0);
218
219                 // TODO: enforce a size limit. May be tricky.
220
221             } finally {
222                 if (cursor != null) {
223                     cursor.close();
224                 }
225             }
226         }
227
228         @Override
229         public View getEditView(
230                 Activity activity, LayoutInflater inflater,
231                 ViewGroup parent, EditCallbacks callbacks) {
232             View result = buildEditView(
233                     activity, inflater, R.layout.tag_edit_image, parent, callbacks);
234             ((ImageView) result.findViewById(R.id.image)).setImageBitmap(getValueInternal());
235             result.setOnClickListener(this);
236             return result;
237         }
238
239         @Override
240         public void writeToParcel(Parcel out, int flags) {
241             super.writeToParcel(out, flags);
242             out.writeString(mCurrentPath);
243             out.writeParcelable(mCachedValue, flags);
244         }
245
246         @SuppressWarnings("unused")
247         public static final Parcelable.Creator<ImageRecordEditInfo> CREATOR =
248                 new Parcelable.Creator<ImageRecordEditInfo>() {
249             @Override
250             public ImageRecordEditInfo createFromParcel(Parcel in) {
251                 return new ImageRecordEditInfo(in);
252             }
253
254             @Override
255             public ImageRecordEditInfo[] newArray(int size) {
256                 return new ImageRecordEditInfo[size];
257             }
258         };
259
260         @Override
261         public int describeContents() {
262             return 0;
263         }
264
265         @Override
266         public void onClick(View target) {
267             if (this == target.getTag()) {
268                 mCallbacks.startPickForRecord(this, getPickIntent());
269             } else {
270                 super.onClick(target);
271             }
272         }
273     }
274 }