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