blob: 72aef85bbf7896d30cbad1f9452071bec1830eb3 [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;
26import android.content.ComponentName;
Jeff Hamilton79495342010-10-18 13:13:10 -050027import android.content.Intent;
Jason parks40d9a0c2010-10-26 14:42:45 -050028import android.content.ServiceConnection;
Ben Komalo9330d7c2010-10-19 14:16:49 -070029import android.content.res.AssetFileDescriptor;
Jeff Hamilton0215c982010-10-18 17:27:21 -050030import android.database.Cursor;
Jeff Hamiltonf34e4d52010-10-21 17:42:09 -050031import android.media.AudioManager;
Ben Komalo9330d7c2010-10-19 14:16:49 -070032import android.media.MediaPlayer;
Jeff Hamilton0215c982010-10-18 17:27:21 -050033import android.net.Uri;
34import android.nfc.FormatException;
Jeff Hamilton79495342010-10-18 13:13:10 -050035import android.nfc.NdefMessage;
36import android.nfc.NdefTag;
37import android.nfc.NfcAdapter;
Jeff Hamilton0215c982010-10-18 17:27:21 -050038import android.os.AsyncTask;
Jeff Hamilton79495342010-10-18 13:13:10 -050039import android.os.Bundle;
Jason parks40d9a0c2010-10-26 14:42:45 -050040import android.os.IBinder;
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -050041import android.text.format.DateUtils;
Jeff Hamilton79495342010-10-18 13:13:10 -050042import android.util.Log;
Jeff Hamilton79495342010-10-18 13:13:10 -050043import android.view.LayoutInflater;
44import android.view.View;
45import android.view.View.OnClickListener;
46import android.view.WindowManager;
47import android.widget.Button;
48import android.widget.CheckBox;
49import android.widget.ImageView;
50import android.widget.LinearLayout;
51import android.widget.TextView;
52
Ben Komalo9330d7c2010-10-19 14:16:49 -070053import java.io.IOException;
Jason parks40d9a0c2010-10-26 14:42:45 -050054import java.util.List;
Ben Komalo9330d7c2010-10-19 14:16:49 -070055
Jeff Hamilton79495342010-10-18 13:13:10 -050056/**
57 * An {@link Activity} which handles a broadcast of a new tag that the device just discovered.
58 */
Jeff Hamilton52baa662010-10-22 01:42:44 -050059public class TagViewer extends Activity implements OnClickListener {
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -050060 static final String TAG = "SaveTag";
Jeff Hamilton79495342010-10-18 13:13:10 -050061 static final String EXTRA_TAG_DB_ID = "db_id";
62 static final String EXTRA_MESSAGE = "msg";
63
64 /** This activity will finish itself in this amount of time if the user doesn't do anything. */
Jeff Hamilton0215c982010-10-18 17:27:21 -050065 static final int ACTIVITY_TIMEOUT_MS = 5 * 1000;
Jeff Hamilton79495342010-10-18 13:13:10 -050066
Jeff Hamilton0215c982010-10-18 17:27:21 -050067 Uri mTagUri;
Jeff Hamilton79495342010-10-18 13:13:10 -050068 ImageView mIcon;
69 TextView mTitle;
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -050070 TextView mDate;
Jeff Hamilton79495342010-10-18 13:13:10 -050071 CheckBox mStar;
72 Button mDeleteButton;
Jeff Hamilton0215c982010-10-18 17:27:21 -050073 Button mDoneButton;
Jason parks21638752010-10-21 17:22:16 -050074 NdefTag mTag = null;
Jeff Hamilton0215c982010-10-18 17:27:21 -050075 LinearLayout mTagContent;
Jeff Hamilton79495342010-10-18 13:13:10 -050076
77 @Override
78 protected void onCreate(Bundle savedInstanceState) {
79 super.onCreate(savedInstanceState);
80
81 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
82 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
Jeff Hamilton79495342010-10-18 13:13:10 -050083 | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
Jeff Hamilton79495342010-10-18 13:13:10 -050084 );
85
86 setContentView(R.layout.tag_viewer);
87
Jeff Hamilton0215c982010-10-18 17:27:21 -050088 mTagContent = (LinearLayout) findViewById(R.id.list);
Jeff Hamilton79495342010-10-18 13:13:10 -050089 mTitle = (TextView) findViewById(R.id.title);
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -050090 mDate = (TextView) findViewById(R.id.date);
Jeff Hamilton79495342010-10-18 13:13:10 -050091 mIcon = (ImageView) findViewById(R.id.icon);
92 mStar = (CheckBox) findViewById(R.id.star);
Jeff Hamilton0215c982010-10-18 17:27:21 -050093 mDeleteButton = (Button) findViewById(R.id.button_delete);
94 mDoneButton = (Button) findViewById(R.id.button_done);
Jeff Hamilton79495342010-10-18 13:13:10 -050095
96 mDeleteButton.setOnClickListener(this);
Jeff Hamilton0215c982010-10-18 17:27:21 -050097 mDoneButton.setOnClickListener(this);
Jeff Hamilton52baa662010-10-22 01:42:44 -050098 mStar.setOnClickListener(this);
Jeff Hamilton79495342010-10-18 13:13:10 -050099 mIcon.setImageResource(R.drawable.ic_launcher_nfc);
100
Jeff Hamilton0215c982010-10-18 17:27:21 -0500101 resolveIntent(getIntent());
102 }
103
Jason parks40d9a0c2010-10-26 14:42:45 -0500104 @Override
105 public void onRestart() {
106 super.onRestart();
107 if (mTagUri == null) {
108 // Someone how the user was fast enough to navigate away from the activity
109 // before the service was able to save the tag and call back onto this
110 // activity with the pending intent. Since we don't know what do display here
111 // just finish the activity.
112 finish();
113 }
114 }
115
116 @Override
117 public void onStop() {
118 super.onStop();
119
120 PendingIntent pending = getPendingIntent();
121 pending.cancel();
122 }
123
124 private PendingIntent getPendingIntent() {
125 Intent callback = new Intent();
126 callback.setClass(this, TagViewer.class);
127 callback.setAction(Intent.ACTION_VIEW);
128 callback.setFlags(Intent. FLAG_ACTIVITY_CLEAR_TOP);
129
130 return PendingIntent.getActivity(this, 0, callback, PendingIntent.FLAG_CANCEL_CURRENT);
131 }
132
133
Jeff Hamilton0215c982010-10-18 17:27:21 -0500134 void resolveIntent(Intent intent) {
135 // Parse the intent
136 String action = intent.getAction();
137 if (NfcAdapter.ACTION_NDEF_TAG_DISCOVERED.equals(action)) {
Jason parks40d9a0c2010-10-26 14:42:45 -0500138 // When a tag is discovered we send it to the service to be save. We
139 // include a PendingIntent for the service to call back onto. This
140 // will cause this activity to be restarted with onNewIntent(). At
141 // that time we read it from the database and view it.
142
Jeff Hamilton0215c982010-10-18 17:27:21 -0500143 NdefTag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
Jason parks40d9a0c2010-10-26 14:42:45 -0500144
145 PendingIntent pending = getPendingIntent();
146
147 TagService.saveTag(this, tag, false, pending);
Jeff Hamilton79495342010-10-18 13:13:10 -0500148
Jeff Hamilton0215c982010-10-18 17:27:21 -0500149 // Setup the views
150 setTitle(R.string.title_scanned_tag);
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500151 mDate.setVisibility(View.GONE);
Jeff Hamilton52baa662010-10-22 01:42:44 -0500152 mStar.setChecked(false);
153 mStar.setEnabled(true);
Jeff Hamilton79495342010-10-18 13:13:10 -0500154
Ben Komalo9330d7c2010-10-19 14:16:49 -0700155 // Play notification.
156 try {
157 MediaPlayer player = new MediaPlayer();
158 AssetFileDescriptor file = getResources().openRawResourceFd(
159 R.raw.discovered_tag_notification);
160 player.setDataSource(
161 file.getFileDescriptor(),
162 file.getStartOffset(),
163 file.getLength());
164 file.close();
Jeff Hamiltonf34e4d52010-10-21 17:42:09 -0500165 player.setAudioStreamType(AudioManager.STREAM_MUSIC);
Ben Komalo9330d7c2010-10-19 14:16:49 -0700166 player.prepare();
167 player.start();
168 } catch (IOException ex) {
169 Log.w(TAG, "Sound creation failed for tag discovery");
170 }
171
Jeff Hamilton0215c982010-10-18 17:27:21 -0500172 } else if (Intent.ACTION_VIEW.equals(action)) {
173 // Setup the views
174 setTitle(R.string.title_existing_tag);
175 mStar.setVisibility(View.VISIBLE);
Jeff Hamilton52baa662010-10-22 01:42:44 -0500176 mStar.setEnabled(false); // it's reenabled when the async load completes
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500177 mDate.setVisibility(View.VISIBLE);
Jeff Hamilton0215c982010-10-18 17:27:21 -0500178
179 // Read the tag from the database asynchronously
180 mTagUri = intent.getData();
181 new LoadTagTask().execute(mTagUri);
182 } else {
183 Log.e(TAG, "Unknown intent " + intent);
Jeff Hamilton79495342010-10-18 13:13:10 -0500184 finish();
185 return;
186 }
Jeff Hamilton79495342010-10-18 13:13:10 -0500187 }
188
Jeff Hamilton0215c982010-10-18 17:27:21 -0500189 void buildTagViews(NdefMessage[] msgs) {
Jeff Hamilton79495342010-10-18 13:13:10 -0500190 if (msgs == null || msgs.length == 0) {
191 return;
192 }
193
Jeff Hamilton0215c982010-10-18 17:27:21 -0500194 LayoutInflater inflater = LayoutInflater.from(this);
195 LinearLayout content = mTagContent;
Jeff Hamilton79495342010-10-18 13:13:10 -0500196
Jeff Hamilton0215c982010-10-18 17:27:21 -0500197 // Clear out any old views in the content area, for example if you scan two tags in a row.
198 content.removeAllViews();
199
200 // Parse the first message in the list
201 //TODO figure out what to do when/if we support multiple messages per tag
202 ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msgs[0]);
Jeff Hamilton79495342010-10-18 13:13:10 -0500203
204 // Build views for all of the sub records
Jason parks40d9a0c2010-10-26 14:42:45 -0500205 List<ParsedNdefRecord> records = parsedMsg.getRecords();
206 final int size = records.size();
207
208 for (int i = 0 ; i < size ; i++) {
209 ParsedNdefRecord record = records.get(i);
210 content.addView(record.getView(this, inflater, content, i));
Jeff Hamilton0215c982010-10-18 17:27:21 -0500211 inflater.inflate(R.layout.tag_divider, content, true);
Jeff Hamilton79495342010-10-18 13:13:10 -0500212 }
213 }
214
215 @Override
Jeff Hamilton0215c982010-10-18 17:27:21 -0500216 public void onNewIntent(Intent intent) {
Jason parks40d9a0c2010-10-26 14:42:45 -0500217 setIntent(intent);
Jeff Hamilton0215c982010-10-18 17:27:21 -0500218 resolveIntent(intent);
219 }
220
221 @Override
Jeff Hamilton79495342010-10-18 13:13:10 -0500222 public void setTitle(CharSequence title) {
223 mTitle.setText(title);
224 }
225
226 @Override
227 public void onClick(View view) {
228 if (view == mDeleteButton) {
Jeff Hamilton0215c982010-10-18 17:27:21 -0500229 if (mTagUri == null) {
230 // The tag hasn't been saved yet, so indicate it shouldn't be saved
Jason parks21638752010-10-21 17:22:16 -0500231 mTag = null;
Jeff Hamilton0215c982010-10-18 17:27:21 -0500232 finish();
233 } else {
234 // The tag came from the database, start a service to delete it
Jeff Hamilton52baa662010-10-22 01:42:44 -0500235 TagService.delete(this, mTagUri);
Jeff Hamilton0215c982010-10-18 17:27:21 -0500236 finish();
237 }
238 } else if (view == mDoneButton) {
Jeff Hamilton79495342010-10-18 13:13:10 -0500239 finish();
Jeff Hamilton52baa662010-10-22 01:42:44 -0500240 } else if (view == mStar) {
241 if (mTagUri != null) {
242 TagService.setStar(this, mTagUri, mStar.isChecked());
243 }
Jeff Hamilton79495342010-10-18 13:13:10 -0500244 }
245 }
246
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500247 interface ViewTagQuery {
248 final static String[] PROJECTION = new String[] {
249 NdefMessages.BYTES, // 0
250 NdefMessages.STARRED, // 1
251 NdefMessages.DATE, // 2
252 };
253
254 static final int COLUMN_BYTES = 0;
255 static final int COLUMN_STARRED = 1;
256 static final int COLUMN_DATE = 2;
257 }
258
Jeff Hamilton0215c982010-10-18 17:27:21 -0500259 /**
260 * Loads a tag from the database, parses it, and builds the views
261 */
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500262 final class LoadTagTask extends AsyncTask<Uri, Void, Cursor> {
Jeff Hamilton0215c982010-10-18 17:27:21 -0500263 @Override
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500264 public Cursor doInBackground(Uri... args) {
265 Cursor cursor = getContentResolver().query(args[0], ViewTagQuery.PROJECTION,
Jeff Hamilton0215c982010-10-18 17:27:21 -0500266 null, null, null);
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500267
268 // Ensure the cursor loads its window
269 if (cursor != null) cursor.getCount();
270 return cursor;
271 }
272
273 @Override
274 public void onPostExecute(Cursor cursor) {
275 NdefMessage msg = null;
Jeff Hamilton0215c982010-10-18 17:27:21 -0500276 try {
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500277 if (cursor != null && cursor.moveToFirst()) {
278 msg = new NdefMessage(cursor.getBlob(ViewTagQuery.COLUMN_BYTES));
279 if (msg != null) {
280 mDate.setText(DateUtils.getRelativeTimeSpanString(TagViewer.this,
281 cursor.getLong(ViewTagQuery.COLUMN_DATE)));
282 mStar.setChecked(cursor.getInt(ViewTagQuery.COLUMN_STARRED) != 0);
Jeff Hamilton52baa662010-10-22 01:42:44 -0500283 mStar.setEnabled(true);
Jeff Hamiltonf979f1c2010-10-18 18:06:53 -0500284 buildTagViews(new NdefMessage[] { msg });
285 }
Jeff Hamilton0215c982010-10-18 17:27:21 -0500286 }
287 } catch (FormatException e) {
288 Log.e(TAG, "invalid tag format", e);
289 } finally {
290 if (cursor != null) cursor.close();
291 }
Jeff Hamilton0215c982010-10-18 17:27:21 -0500292 }
293 }
Jeff Hamilton79495342010-10-18 13:13:10 -0500294}