blob: 4d4e8703da28557d5363e9800ef3f1571e1db9ff [file] [log] [blame]
Jeff Hamilton79495342010-10-18 13:13:10 -05001/*
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;
18
19import com.android.apps.tag.message.NdefMessageParser;
20import com.android.apps.tag.message.ParsedNdefMessage;
Jeff Hamilton0215c982010-10-18 17:27:21 -050021import com.android.apps.tag.provider.TagContract.NdefMessages;
Jeff Hamilton79495342010-10-18 13:13:10 -050022import com.android.apps.tag.record.ParsedNdefRecord;
23
24import android.app.Activity;
Jason parks40d9a0c2010-10-26 14:42:45 -050025import android.app.PendingIntent;
Jason parksc2bab522010-11-02 13:33:01 -050026import android.content.BroadcastReceiver;
Jeff Hamiltona6f38c72010-12-17 16:16:21 -060027import android.content.ContentUris;
Jeff Hamilton383e6d12010-10-29 16:21:55 -050028import android.content.Context;
Jeff Hamilton79495342010-10-18 13:13:10 -050029import android.content.Intent;
Jason parksc2bab522010-11-02 13:33:01 -050030import android.content.IntentFilter;
Jeff Hamiltona6f38c72010-12-17 16:16:21 -060031import android.content.SharedPreferences;
Ben Komaloc27e0b72010-12-23 15:25:58 -050032import android.content.res.AssetFileDescriptor;
Jeff Hamilton0215c982010-10-18 17:27:21 -050033import android.database.Cursor;
Jeff Hamiltonf34e4d52010-10-21 17:42:09 -050034import android.media.AudioManager;
Ben Komalo9330d7c2010-10-19 14:16:49 -070035import android.media.MediaPlayer;
Jeff Hamilton0215c982010-10-18 17:27:21 -050036import android.net.Uri;
37import android.nfc.FormatException;
Jeff Hamilton79495342010-10-18 13:13:10 -050038import android.nfc.NdefMessage;
Jeff Hamiltona29dc1b2010-10-28 17:44:37 -050039import android.nfc.NdefRecord;
Jeff Hamilton79495342010-10-18 13:13:10 -050040import android.nfc.NfcAdapter;
Jeff Hamiltona6f38c72010-12-17 16:16:21 -060041import android.nfc.Tag;
Jeff Hamilton42aa0292011-01-21 13:30:53 -060042import android.nfc.tech.Ndef;
43import android.nfc.tech.NdefFormatable;
Jeff Hamilton0215c982010-10-18 17:27:21 -050044import android.os.AsyncTask;
Jeff Hamilton79495342010-10-18 13:13:10 -050045import android.os.Bundle;
Jeff Hamiltona29dc1b2010-10-28 17:44:37 -050046import android.os.Parcelable;
Jeff Hamilton383e6d12010-10-29 16:21:55 -050047import android.os.PowerManager;
48import android.os.PowerManager.WakeLock;
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -050049import android.text.format.DateUtils;
Jeff Hamilton79495342010-10-18 13:13:10 -050050import android.util.Log;
Jeff Hamilton79495342010-10-18 13:13:10 -050051import android.view.LayoutInflater;
52import android.view.View;
53import android.view.View.OnClickListener;
54import android.view.WindowManager;
55import android.widget.Button;
56import android.widget.CheckBox;
57import android.widget.ImageView;
58import android.widget.LinearLayout;
59import android.widget.TextView;
Ben Komalo9299bbc2010-11-01 13:45:40 -070060import android.widget.Toast;
Jeff Hamilton79495342010-10-18 13:13:10 -050061
Ben Komaloc27e0b72010-12-23 15:25:58 -050062import java.io.IOException;
Jason parks40d9a0c2010-10-26 14:42:45 -050063import java.util.List;
Ben Komalo9330d7c2010-10-19 14:16:49 -070064
Jeff Hamilton79495342010-10-18 13:13:10 -050065/**
66 * An {@link Activity} which handles a broadcast of a new tag that the device just discovered.
67 */
Jeff Hamilton52baa662010-10-22 01:42:44 -050068public class TagViewer extends Activity implements OnClickListener {
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -050069 static final String TAG = "SaveTag";
Jeff Hamilton79495342010-10-18 13:13:10 -050070 static final String EXTRA_TAG_DB_ID = "db_id";
71 static final String EXTRA_MESSAGE = "msg";
Jason parks4a9765f2010-10-29 16:14:40 -050072 static final String EXTRA_KEEP_TITLE = "keepTitle";
Jeff Hamilton79495342010-10-18 13:13:10 -050073
Jeff Hamilton39523c72010-11-03 19:37:58 -050074 static final boolean SHOW_OVER_LOCK_SCREEN = false;
75
Jeff Hamilton79495342010-10-18 13:13:10 -050076 /** This activity will finish itself in this amount of time if the user doesn't do anything. */
Jeff Hamilton383e6d12010-10-29 16:21:55 -050077 static final int ACTIVITY_TIMEOUT_MS = 7 * 1000;
Jeff Hamilton79495342010-10-18 13:13:10 -050078
Jeff Hamilton0215c982010-10-18 17:27:21 -050079 Uri mTagUri;
Jeff Hamilton79495342010-10-18 13:13:10 -050080 ImageView mIcon;
81 TextView mTitle;
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -050082 TextView mDate;
Jeff Hamilton79495342010-10-18 13:13:10 -050083 CheckBox mStar;
84 Button mDeleteButton;
Jeff Hamilton0215c982010-10-18 17:27:21 -050085 Button mDoneButton;
Jeff Hamilton0215c982010-10-18 17:27:21 -050086 LinearLayout mTagContent;
Jeff Hamilton79495342010-10-18 13:13:10 -050087
Jason parksc2bab522010-11-02 13:33:01 -050088 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
Jeff Hamilton79495342010-10-18 13:13:10 -0500101 @Override
102 protected void onCreate(Bundle savedInstanceState) {
103 super.onCreate(savedInstanceState);
104
Jeff Hamilton39523c72010-11-03 19:37:58 -0500105 if (SHOW_OVER_LOCK_SCREEN) {
106 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
107 }
Jeff Hamilton79495342010-10-18 13:13:10 -0500108
109 setContentView(R.layout.tag_viewer);
110
Jeff Hamilton0215c982010-10-18 17:27:21 -0500111 mTagContent = (LinearLayout) findViewById(R.id.list);
Jeff Hamilton79495342010-10-18 13:13:10 -0500112 mTitle = (TextView) findViewById(R.id.title);
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500113 mDate = (TextView) findViewById(R.id.date);
Jeff Hamilton79495342010-10-18 13:13:10 -0500114 mIcon = (ImageView) findViewById(R.id.icon);
115 mStar = (CheckBox) findViewById(R.id.star);
Jeff Hamilton0215c982010-10-18 17:27:21 -0500116 mDeleteButton = (Button) findViewById(R.id.button_delete);
117 mDoneButton = (Button) findViewById(R.id.button_done);
Jeff Hamilton79495342010-10-18 13:13:10 -0500118
119 mDeleteButton.setOnClickListener(this);
Jeff Hamilton0215c982010-10-18 17:27:21 -0500120 mDoneButton.setOnClickListener(this);
Jeff Hamilton52baa662010-10-22 01:42:44 -0500121 mStar.setOnClickListener(this);
Jeff Hamilton79495342010-10-18 13:13:10 -0500122 mIcon.setImageResource(R.drawable.ic_launcher_nfc);
123
Jeff Hamilton0215c982010-10-18 17:27:21 -0500124 resolveIntent(getIntent());
125 }
126
Jason parks40d9a0c2010-10-26 14:42:45 -0500127 @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();
Jason parksc2bab522010-11-02 13:33:01 -0500145
146 if (mReceiver != null) {
147 unregisterReceiver(mReceiver);
148 mReceiver = null;
149 }
Jason parks40d9a0c2010-10-26 14:42:45 -0500150 }
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);
Jason parks4a9765f2010-10-29 16:14:40 -0500157 callback.putExtra(EXTRA_KEEP_TITLE, true);
Jason parks40d9a0c2010-10-26 14:42:45 -0500158
159 return PendingIntent.getActivity(this, 0, callback, PendingIntent.FLAG_CANCEL_CURRENT);
160 }
161
Jeff Hamilton0215c982010-10-18 17:27:21 -0500162 void resolveIntent(Intent intent) {
163 // Parse the intent
164 String action = intent.getAction();
Jeff Hamilton80e02342010-12-03 17:34:31 -0600165 if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)
Nick Pelly42d74142011-02-01 08:26:41 -0800166 || NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
Jeff Hamilton39523c72010-11-03 19:37:58 -0500167 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);
Jeff Hamilton383e6d12010-10-29 16:21:55 -0500177
Jeff Hamilton39523c72010-11-03 19:37:58 -0500178 if (mReceiver == null) {
179 mReceiver = new ScreenOffReceiver();
180 IntentFilter filter = new IntentFilter();
181 filter.addAction(Intent.ACTION_SCREEN_OFF);
182 registerReceiver(mReceiver, filter);
183 }
Jason parksc2bab522010-11-02 13:33:01 -0500184 }
185
Jeff Hamilton383e6d12010-10-29 16:21:55 -0500186 // When a tag is discovered we send it to the service to be save. We
Jason parks40d9a0c2010-10-26 14:42:45 -0500187 // 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.
Jeff Hamiltona29dc1b2010-10-28 17:44:37 -0500190 Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
191 NdefMessage[] msgs;
Jason parksf38cc722010-11-02 22:50:09 -0500192 if (rawMsgs != null && rawMsgs.length > 0) {
Jeff Hamiltona29dc1b2010-10-28 17:44:37 -0500193 // 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());
Jeff Hamilton79495342010-10-18 13:13:10 -0500206
Jeff Hamilton0215c982010-10-18 17:27:21 -0500207 // Setup the views
208 setTitle(R.string.title_scanned_tag);
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500209 mDate.setVisibility(View.GONE);
Jeff Hamilton52baa662010-10-22 01:42:44 -0500210 mStar.setChecked(false);
211 mStar.setEnabled(true);
Jeff Hamilton79495342010-10-18 13:13:10 -0500212
Ben Komalo9330d7c2010-10-19 14:16:49 -0700213 // Play notification.
Ben Komaloc27e0b72010-12-23 15:25:58 -0500214 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();
Jason parks7cce6552010-11-04 17:11:10 -0500231 }
Ben Komaloc27e0b72010-12-23 15:25:58 -0500232 } 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 }
Ben Komalo9330d7c2010-10-19 14:16:49 -0700239
Jeff Hamilton0215c982010-10-18 17:27:21 -0500240 } else if (Intent.ACTION_VIEW.equals(action)) {
241 // Setup the views
Jason parks4a9765f2010-10-29 16:14:40 -0500242 if (!intent.getBooleanExtra(EXTRA_KEEP_TITLE, false)) {
243 setTitle(R.string.title_existing_tag);
Ben Komalo81e7cec2010-11-01 13:42:23 -0700244 mDate.setVisibility(View.VISIBLE);
Jason parks4a9765f2010-10-29 16:14:40 -0500245 }
246
Jeff Hamilton0215c982010-10-18 17:27:21 -0500247 mStar.setVisibility(View.VISIBLE);
Jeff Hamilton52baa662010-10-22 01:42:44 -0500248 mStar.setEnabled(false); // it's reenabled when the async load completes
Jeff Hamilton0215c982010-10-18 17:27:21 -0500249
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);
Jeff Hamilton79495342010-10-18 13:13:10 -0500255 finish();
256 return;
257 }
Jeff Hamilton79495342010-10-18 13:13:10 -0500258 }
259
Jeff Hamilton0215c982010-10-18 17:27:21 -0500260 void buildTagViews(NdefMessage[] msgs) {
Jeff Hamilton79495342010-10-18 13:13:10 -0500261 if (msgs == null || msgs.length == 0) {
262 return;
263 }
264
Jeff Hamilton0215c982010-10-18 17:27:21 -0500265 LayoutInflater inflater = LayoutInflater.from(this);
266 LinearLayout content = mTagContent;
Jeff Hamilton79495342010-10-18 13:13:10 -0500267
Jeff Hamilton0215c982010-10-18 17:27:21 -0500268 // 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]);
Jeff Hamilton79495342010-10-18 13:13:10 -0500274
275 // Build views for all of the sub records
Jason parks40d9a0c2010-10-26 14:42:45 -0500276 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));
Jeff Hamilton0215c982010-10-18 17:27:21 -0500282 inflater.inflate(R.layout.tag_divider, content, true);
Jeff Hamilton79495342010-10-18 13:13:10 -0500283 }
284 }
285
286 @Override
Jeff Hamilton0215c982010-10-18 17:27:21 -0500287 public void onNewIntent(Intent intent) {
Jason parks40d9a0c2010-10-26 14:42:45 -0500288 setIntent(intent);
Jeff Hamilton0215c982010-10-18 17:27:21 -0500289 resolveIntent(intent);
290 }
291
292 @Override
Jeff Hamilton79495342010-10-18 13:13:10 -0500293 public void setTitle(CharSequence title) {
294 mTitle.setText(title);
295 }
296
297 @Override
298 public void onClick(View view) {
299 if (view == mDeleteButton) {
Jeff Hamilton0215c982010-10-18 17:27:21 -0500300 if (mTagUri == null) {
Jeff Hamilton0215c982010-10-18 17:27:21 -0500301 finish();
302 } else {
303 // The tag came from the database, start a service to delete it
Jeff Hamilton52baa662010-10-22 01:42:44 -0500304 TagService.delete(this, mTagUri);
Jeff Hamilton0215c982010-10-18 17:27:21 -0500305 finish();
306 }
Ben Komalo9299bbc2010-11-01 13:45:40 -0700307 Toast.makeText(this, getResources().getString(R.string.tag_deleted), Toast.LENGTH_SHORT)
308 .show();
Jeff Hamilton0215c982010-10-18 17:27:21 -0500309 } else if (view == mDoneButton) {
Jeff Hamilton79495342010-10-18 13:13:10 -0500310 finish();
Jeff Hamilton52baa662010-10-22 01:42:44 -0500311 } else if (view == mStar) {
312 if (mTagUri != null) {
313 TagService.setStar(this, mTagUri, mStar.isChecked());
314 }
Jeff Hamilton79495342010-10-18 13:13:10 -0500315 }
316 }
317
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500318 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
Jeff Hamilton0215c982010-10-18 17:27:21 -0500330 /**
331 * Loads a tag from the database, parses it, and builds the views
332 */
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500333 final class LoadTagTask extends AsyncTask<Uri, Void, Cursor> {
Jeff Hamilton0215c982010-10-18 17:27:21 -0500334 @Override
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500335 public Cursor doInBackground(Uri... args) {
336 Cursor cursor = getContentResolver().query(args[0], ViewTagQuery.PROJECTION,
Jeff Hamilton0215c982010-10-18 17:27:21 -0500337 null, null, null);
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500338
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;
Jeff Hamilton0215c982010-10-18 17:27:21 -0500347 try {
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500348 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);
Jeff Hamilton52baa662010-10-22 01:42:44 -0500354 mStar.setEnabled(true);
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500355 buildTagViews(new NdefMessage[] { msg });
356 }
Jeff Hamilton0215c982010-10-18 17:27:21 -0500357 }
358 } catch (FormatException e) {
359 Log.e(TAG, "invalid tag format", e);
360 } finally {
361 if (cursor != null) cursor.close();
362 }
Jeff Hamilton0215c982010-10-18 17:27:21 -0500363 }
364 }
Jeff Hamilton79495342010-10-18 13:13:10 -0500365}