e17cf355d7fc9d01b4650b9989e52e31e5a11dea
[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.Context;
27 import android.content.Intent;
28 import android.content.res.AssetFileDescriptor;
29 import android.database.Cursor;
30 import android.media.AudioManager;
31 import android.media.MediaPlayer;
32 import android.net.Uri;
33 import android.nfc.FormatException;
34 import android.nfc.NdefMessage;
35 import android.nfc.NdefRecord;
36 import android.nfc.NdefTag;
37 import android.nfc.NfcAdapter;
38 import android.os.AsyncTask;
39 import android.os.Bundle;
40 import android.os.Parcelable;
41 import android.os.PowerManager;
42 import android.os.PowerManager.WakeLock;
43 import android.text.format.DateUtils;
44 import android.util.Log;
45 import android.view.LayoutInflater;
46 import android.view.View;
47 import android.view.View.OnClickListener;
48 import android.view.WindowManager;
49 import android.widget.Button;
50 import android.widget.CheckBox;
51 import android.widget.ImageView;
52 import android.widget.LinearLayout;
53 import android.widget.TextView;
54 import android.widget.Toast;
55
56 import java.io.IOException;
57 import java.util.List;
58
59 /**
60  * An {@link Activity} which handles a broadcast of a new tag that the device just discovered.
61  */
62 public class TagViewer extends Activity implements OnClickListener {
63     static final String TAG = "SaveTag";
64     static final String EXTRA_TAG_DB_ID = "db_id";
65     static final String EXTRA_MESSAGE = "msg";
66     static final String EXTRA_KEEP_TITLE = "keepTitle";
67
68     /** This activity will finish itself in this amount of time if the user doesn't do anything. */
69     static final int ACTIVITY_TIMEOUT_MS = 7 * 1000;
70
71     Uri mTagUri;
72     ImageView mIcon;
73     TextView mTitle;
74     TextView mDate;
75     CheckBox mStar;
76     Button mDeleteButton;
77     Button mDoneButton;
78     NdefTag mTag = null;
79     LinearLayout mTagContent;
80
81     @Override
82     protected void onCreate(Bundle savedInstanceState) {
83         super.onCreate(savedInstanceState);
84
85         getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
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             // A tag was just scanned so poke the user activity wake lock to keep
140             // the screen on a bit longer in the event that the activity has
141             // hidden the lock screen.
142             PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
143             WakeLock wakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, TAG);
144             // This lock cannot be manually release in onStop() since that may cause a lock
145             // under run exception to be thrown when the timeout hits.
146             wakeLock.acquire(ACTIVITY_TIMEOUT_MS);
147
148             // When a tag is discovered we send it to the service to be save. We
149             // include a PendingIntent for the service to call back onto. This
150             // will cause this activity to be restarted with onNewIntent(). At
151             // that time we read it from the database and view it.
152             Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
153             NdefMessage[] msgs;
154             if (rawMsgs != null) {
155                 // stupid java, need to cast one-by-one
156                 msgs = new NdefMessage[rawMsgs.length];
157                 for (int i=0; i<rawMsgs.length; i++) {
158                     msgs[i] = (NdefMessage) rawMsgs[i];
159                 }
160             } else {
161                 // Unknown tag type
162                 byte[] empty = new byte[] {};
163                 NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty, empty);
164                 NdefMessage msg = new NdefMessage(new NdefRecord[] { record });
165                 msgs = new NdefMessage[] { msg };
166             }
167             TagService.saveMessages(this, msgs, false, getPendingIntent());
168
169             // Setup the views
170             setTitle(R.string.title_scanned_tag);
171             mDate.setVisibility(View.GONE);
172             mStar.setChecked(false);
173             mStar.setEnabled(true);
174
175             // Play notification.
176             try {
177                 MediaPlayer player = new MediaPlayer();
178                 AssetFileDescriptor file = getResources().openRawResourceFd(
179                         R.raw.discovered_tag_notification);
180                 player.setDataSource(
181                         file.getFileDescriptor(),
182                         file.getStartOffset(),
183                         file.getLength());
184                 file.close();
185                 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
186                 player.prepare();
187                 player.start();
188             } catch (IOException ex) {
189                 Log.w(TAG, "Sound creation failed for tag discovery");
190             }
191
192         } else if (Intent.ACTION_VIEW.equals(action)) {
193             // Setup the views
194             if (!intent.getBooleanExtra(EXTRA_KEEP_TITLE, false)) {
195                 setTitle(R.string.title_existing_tag);
196                 mDate.setVisibility(View.VISIBLE);
197             }
198
199             mStar.setVisibility(View.VISIBLE);
200             mStar.setEnabled(false); // it's reenabled when the async load completes
201
202             // Read the tag from the database asynchronously
203             mTagUri = intent.getData();
204             new LoadTagTask().execute(mTagUri);
205         } else {
206             Log.e(TAG, "Unknown intent " + intent);
207             finish();
208             return;
209         }
210     }
211
212     void buildTagViews(NdefMessage[] msgs) {
213         if (msgs == null || msgs.length == 0) {
214             return;
215         }
216
217         LayoutInflater inflater = LayoutInflater.from(this);
218         LinearLayout content = mTagContent;
219
220         // Clear out any old views in the content area, for example if you scan two tags in a row.
221         content.removeAllViews();
222
223         // Parse the first message in the list
224         //TODO figure out what to do when/if we support multiple messages per tag
225         ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msgs[0]);
226
227         // Build views for all of the sub records
228         List<ParsedNdefRecord> records = parsedMsg.getRecords();
229         final int size = records.size();
230
231         for (int i = 0 ; i < size ; i++) {
232             ParsedNdefRecord record = records.get(i);
233             content.addView(record.getView(this, inflater, content, i));
234             inflater.inflate(R.layout.tag_divider, content, true);
235         }
236     }
237
238     @Override
239     public void onNewIntent(Intent intent) {
240         setIntent(intent);
241         resolveIntent(intent);
242     }
243
244     @Override
245     public void setTitle(CharSequence title) {
246         mTitle.setText(title);
247     }
248
249     @Override
250     public void onClick(View view) {
251         if (view == mDeleteButton) {
252             if (mTagUri == null) {
253                 // The tag hasn't been saved yet, so indicate it shouldn't be saved
254                 mTag = null;
255                 finish();
256             } else {
257                 // The tag came from the database, start a service to delete it
258                 TagService.delete(this, mTagUri);
259                 finish();
260             }
261             Toast.makeText(this, getResources().getString(R.string.tag_deleted), Toast.LENGTH_SHORT)
262                     .show();
263         } else if (view == mDoneButton) {
264             finish();
265         } else if (view == mStar) {
266             if (mTagUri != null) {
267                 TagService.setStar(this, mTagUri, mStar.isChecked());
268             }
269         }
270     }
271
272     interface ViewTagQuery {
273         final static String[] PROJECTION = new String[] {
274                 NdefMessages.BYTES, // 0
275                 NdefMessages.STARRED, // 1
276                 NdefMessages.DATE, // 2
277         };
278
279         static final int COLUMN_BYTES = 0;
280         static final int COLUMN_STARRED = 1;
281         static final int COLUMN_DATE = 2;
282     }
283
284     /**
285      * Loads a tag from the database, parses it, and builds the views
286      */
287     final class LoadTagTask extends AsyncTask<Uri, Void, Cursor> {
288         @Override
289         public Cursor doInBackground(Uri... args) {
290             Cursor cursor = getContentResolver().query(args[0], ViewTagQuery.PROJECTION,
291                     null, null, null);
292
293             // Ensure the cursor loads its window
294             if (cursor != null) cursor.getCount();
295             return cursor;
296         }
297
298         @Override
299         public void onPostExecute(Cursor cursor) {
300             NdefMessage msg = null;
301             try {
302                 if (cursor != null && cursor.moveToFirst()) {
303                     msg = new NdefMessage(cursor.getBlob(ViewTagQuery.COLUMN_BYTES));
304                     if (msg != null) {
305                         mDate.setText(DateUtils.getRelativeTimeSpanString(TagViewer.this,
306                                 cursor.getLong(ViewTagQuery.COLUMN_DATE)));
307                         mStar.setChecked(cursor.getInt(ViewTagQuery.COLUMN_STARRED) != 0);
308                         mStar.setEnabled(true);
309                         buildTagViews(new NdefMessage[] { msg });
310                     }
311                 }
312             } catch (FormatException e) {
313                 Log.e(TAG, "invalid tag format", e);
314             } finally {
315                 if (cursor != null) cursor.close();
316             }
317         }
318     }
319 }