e66030e127db403ef7c92781938e4c457216c72e
[android/platform/packages/apps/Tag.git] / src / com / android / apps / tag / TagViewer.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;
18
19 import com.android.apps.tag.message.NdefMessageParser;
20 import com.android.apps.tag.message.ParsedNdefMessage;
21 import com.android.apps.tag.provider.TagContract.NdefMessages;
22 import com.android.apps.tag.record.ParsedNdefRecord;
23
24 import android.app.Activity;
25 import android.app.PendingIntent;
26 import android.content.Intent;
27 import android.content.res.AssetFileDescriptor;
28 import android.database.Cursor;
29 import android.media.AudioManager;
30 import android.media.MediaPlayer;
31 import android.net.Uri;
32 import android.nfc.FormatException;
33 import android.nfc.NdefMessage;
34 import android.nfc.NdefRecord;
35 import android.nfc.NdefTag;
36 import android.nfc.NfcAdapter;
37 import android.os.AsyncTask;
38 import android.os.Bundle;
39 import android.os.Parcelable;
40 import android.text.format.DateUtils;
41 import android.util.Log;
42 import android.view.LayoutInflater;
43 import android.view.View;
44 import android.view.View.OnClickListener;
45 import android.view.WindowManager;
46 import android.widget.Button;
47 import android.widget.CheckBox;
48 import android.widget.ImageView;
49 import android.widget.LinearLayout;
50 import android.widget.TextView;
51 import android.widget.Toast;
52
53 import java.io.IOException;
54 import java.util.List;
55
56 /**
57  * An {@link Activity} which handles a broadcast of a new tag that the device just discovered.
58  */
59 public class TagViewer extends Activity implements OnClickListener {
60     static final String TAG = "SaveTag";
61     static final String EXTRA_TAG_DB_ID = "db_id";
62     static final String EXTRA_MESSAGE = "msg";
63     static final String EXTRA_KEEP_TITLE = "keepTitle";
64
65     /** This activity will finish itself in this amount of time if the user doesn't do anything. */
66     static final int ACTIVITY_TIMEOUT_MS = 5 * 1000;
67
68     Uri mTagUri;
69     ImageView mIcon;
70     TextView mTitle;
71     TextView mDate;
72     CheckBox mStar;
73     Button mDeleteButton;
74     Button mDoneButton;
75     NdefTag mTag = null;
76     LinearLayout mTagContent;
77
78     @Override
79     protected void onCreate(Bundle savedInstanceState) {
80         super.onCreate(savedInstanceState);
81
82         getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
83                 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
84                 | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
85         );
86
87         setContentView(R.layout.tag_viewer);
88
89         mTagContent = (LinearLayout) findViewById(R.id.list);
90         mTitle = (TextView) findViewById(R.id.title);
91         mDate = (TextView) findViewById(R.id.date);
92         mIcon = (ImageView) findViewById(R.id.icon);
93         mStar = (CheckBox) findViewById(R.id.star);
94         mDeleteButton = (Button) findViewById(R.id.button_delete);
95         mDoneButton = (Button) findViewById(R.id.button_done);
96
97         mDeleteButton.setOnClickListener(this);
98         mDoneButton.setOnClickListener(this);
99         mStar.setOnClickListener(this);
100         mIcon.setImageResource(R.drawable.ic_launcher_nfc);
101
102         resolveIntent(getIntent());
103     }
104
105     @Override
106     public void onRestart() {
107         super.onRestart();
108         if (mTagUri == null) {
109             // Someone how the user was fast enough to navigate away from the activity
110             // before the service was able to save the tag and call back onto this
111             // activity with the pending intent. Since we don't know what do display here
112             // just finish the activity.
113             finish();
114         }
115     }
116
117     @Override
118     public void onStop() {
119         super.onStop();
120
121         PendingIntent pending = getPendingIntent();
122         pending.cancel();
123     }
124
125     private PendingIntent getPendingIntent() {
126         Intent callback = new Intent();
127         callback.setClass(this, TagViewer.class);
128         callback.setAction(Intent.ACTION_VIEW);
129         callback.setFlags(Intent. FLAG_ACTIVITY_CLEAR_TOP);
130         callback.putExtra(EXTRA_KEEP_TITLE, true);
131
132         return PendingIntent.getActivity(this, 0, callback, PendingIntent.FLAG_CANCEL_CURRENT);
133     }
134
135     void resolveIntent(Intent intent) {
136         // Parse the intent
137         String action = intent.getAction();
138         if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
139             // When a tag is discovered we send it to the service to be saved. We
140             // include a PendingIntent for the service to call back onto. This
141             // will cause this activity to be restarted with onNewIntent(). At
142             // that time we read it from the database and view it.
143             Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
144             NdefMessage[] msgs;
145             if (rawMsgs != null) {
146                 // stupid java, need to cast one-by-one
147                 msgs = new NdefMessage[rawMsgs.length];
148                 for (int i=0; i<rawMsgs.length; i++) {
149                     msgs[i] = (NdefMessage) rawMsgs[i];
150                 }
151             } else {
152                 // Unknown tag type
153                 byte[] empty = new byte[] {};
154                 NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty, empty);
155                 NdefMessage msg = new NdefMessage(new NdefRecord[] { record });
156                 msgs = new NdefMessage[] { msg };
157             }
158             TagService.saveMessages(this, msgs, false, getPendingIntent());
159
160             // Setup the views
161             setTitle(R.string.title_scanned_tag);
162             mDate.setVisibility(View.GONE);
163             mStar.setChecked(false);
164             mStar.setEnabled(true);
165
166             // Play notification.
167             try {
168                 MediaPlayer player = new MediaPlayer();
169                 AssetFileDescriptor file = getResources().openRawResourceFd(
170                         R.raw.discovered_tag_notification);
171                 player.setDataSource(
172                         file.getFileDescriptor(),
173                         file.getStartOffset(),
174                         file.getLength());
175                 file.close();
176                 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
177                 player.prepare();
178                 player.start();
179             } catch (IOException ex) {
180                 Log.w(TAG, "Sound creation failed for tag discovery");
181             }
182
183         } else if (Intent.ACTION_VIEW.equals(action)) {
184             // Setup the views
185             if (!intent.getBooleanExtra(EXTRA_KEEP_TITLE, false)) {
186                 setTitle(R.string.title_existing_tag);
187                 mDate.setVisibility(View.VISIBLE);
188             }
189
190             mStar.setVisibility(View.VISIBLE);
191             mStar.setEnabled(false); // it's reenabled when the async load completes
192
193             // Read the tag from the database asynchronously
194             mTagUri = intent.getData();
195             new LoadTagTask().execute(mTagUri);
196         } else {
197             Log.e(TAG, "Unknown intent " + intent);
198             finish();
199             return;
200         }
201     }
202
203     void buildTagViews(NdefMessage[] msgs) {
204         if (msgs == null || msgs.length == 0) {
205             return;
206         }
207
208         LayoutInflater inflater = LayoutInflater.from(this);
209         LinearLayout content = mTagContent;
210
211         // Clear out any old views in the content area, for example if you scan two tags in a row.
212         content.removeAllViews();
213
214         // Parse the first message in the list
215         //TODO figure out what to do when/if we support multiple messages per tag
216         ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msgs[0]);
217
218         // Build views for all of the sub records
219         List<ParsedNdefRecord> records = parsedMsg.getRecords();
220         final int size = records.size();
221
222         for (int i = 0 ; i < size ; i++) {
223             ParsedNdefRecord record = records.get(i);
224             content.addView(record.getView(this, inflater, content, i));
225             inflater.inflate(R.layout.tag_divider, content, true);
226         }
227     }
228
229     @Override
230     public void onNewIntent(Intent intent) {
231         setIntent(intent);
232         resolveIntent(intent);
233     }
234
235     @Override
236     public void setTitle(CharSequence title) {
237         mTitle.setText(title);
238     }
239
240     @Override
241     public void onClick(View view) {
242         if (view == mDeleteButton) {
243             if (mTagUri == null) {
244                 // The tag hasn't been saved yet, so indicate it shouldn't be saved
245                 mTag = null;
246                 finish();
247             } else {
248                 // The tag came from the database, start a service to delete it
249                 TagService.delete(this, mTagUri);
250                 finish();
251             }
252             Toast.makeText(this, getResources().getString(R.string.tag_deleted), Toast.LENGTH_SHORT)
253                     .show();
254         } else if (view == mDoneButton) {
255             finish();
256         } else if (view == mStar) {
257             if (mTagUri != null) {
258                 TagService.setStar(this, mTagUri, mStar.isChecked());
259             }
260         }
261     }
262
263     interface ViewTagQuery {
264         final static String[] PROJECTION = new String[] {
265                 NdefMessages.BYTES, // 0
266                 NdefMessages.STARRED, // 1
267                 NdefMessages.DATE, // 2
268         };
269
270         static final int COLUMN_BYTES = 0;
271         static final int COLUMN_STARRED = 1;
272         static final int COLUMN_DATE = 2;
273     }
274
275     /**
276      * Loads a tag from the database, parses it, and builds the views
277      */
278     final class LoadTagTask extends AsyncTask<Uri, Void, Cursor> {
279         @Override
280         public Cursor doInBackground(Uri... args) {
281             Cursor cursor = getContentResolver().query(args[0], ViewTagQuery.PROJECTION,
282                     null, null, null);
283
284             // Ensure the cursor loads its window
285             if (cursor != null) cursor.getCount();
286             return cursor;
287         }
288
289         @Override
290         public void onPostExecute(Cursor cursor) {
291             NdefMessage msg = null;
292             try {
293                 if (cursor != null && cursor.moveToFirst()) {
294                     msg = new NdefMessage(cursor.getBlob(ViewTagQuery.COLUMN_BYTES));
295                     if (msg != null) {
296                         mDate.setText(DateUtils.getRelativeTimeSpanString(TagViewer.this,
297                                 cursor.getLong(ViewTagQuery.COLUMN_DATE)));
298                         mStar.setChecked(cursor.getInt(ViewTagQuery.COLUMN_STARRED) != 0);
299                         mStar.setEnabled(true);
300                         buildTagViews(new NdefMessage[] { msg });
301                     }
302                 }
303             } catch (FormatException e) {
304                 Log.e(TAG, "invalid tag format", e);
305             } finally {
306                 if (cursor != null) cursor.close();
307             }
308         }
309     }
310 }