blob: fd3fe2e83e515531d9734a97a188e929aeb0f027 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.apps.tag;
import com.android.apps.tag.message.NdefMessageParser;
import com.android.apps.tag.message.ParsedNdefMessage;
import com.android.apps.tag.provider.TagContract.NdefMessages;
import com.android.apps.tag.record.ParsedNdefRecord;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.nfc.tech.NdefFormatable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.util.List;
/**
* An {@link Activity} which handles a broadcast of a new tag that the device just discovered.
*/
public class TagViewer extends Activity implements OnClickListener {
static final String TAG = "SaveTag";
static final String EXTRA_TAG_DB_ID = "db_id";
static final String EXTRA_MESSAGE = "msg";
static final String EXTRA_KEEP_TITLE = "keepTitle";
static final boolean SHOW_OVER_LOCK_SCREEN = false;
/** This activity will finish itself in this amount of time if the user doesn't do anything. */
static final int ACTIVITY_TIMEOUT_MS = 7 * 1000;
Uri mTagUri;
ImageView mIcon;
TextView mTitle;
TextView mDate;
CheckBox mStar;
Button mDeleteButton;
Button mDoneButton;
LinearLayout mTagContent;
BroadcastReceiver mReceiver;
private class ScreenOffReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
if (!isFinishing()) {
finish();
}
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (SHOW_OVER_LOCK_SCREEN) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
}
setContentView(R.layout.tag_viewer);
mTagContent = (LinearLayout) findViewById(R.id.list);
mTitle = (TextView) findViewById(R.id.title);
mDate = (TextView) findViewById(R.id.date);
mIcon = (ImageView) findViewById(R.id.icon);
mStar = (CheckBox) findViewById(R.id.star);
mDeleteButton = (Button) findViewById(R.id.button_delete);
mDoneButton = (Button) findViewById(R.id.button_done);
mDeleteButton.setOnClickListener(this);
mDoneButton.setOnClickListener(this);
mStar.setOnClickListener(this);
mIcon.setImageResource(R.drawable.ic_launcher_nfc);
resolveIntent(getIntent());
}
@Override
public void onRestart() {
super.onRestart();
if (mTagUri == null) {
// Someone how the user was fast enough to navigate away from the activity
// before the service was able to save the tag and call back onto this
// activity with the pending intent. Since we don't know what do display here
// just finish the activity.
finish();
}
}
@Override
public void onStop() {
super.onStop();
PendingIntent pending = getPendingIntent();
pending.cancel();
if (mReceiver != null) {
unregisterReceiver(mReceiver);
mReceiver = null;
}
}
private PendingIntent getPendingIntent() {
Intent callback = new Intent();
callback.setClass(this, TagViewer.class);
callback.setAction(Intent.ACTION_VIEW);
callback.setFlags(Intent. FLAG_ACTIVITY_CLEAR_TOP);
callback.putExtra(EXTRA_KEEP_TITLE, true);
return PendingIntent.getActivity(this, 0, callback, PendingIntent.FLAG_CANCEL_CURRENT);
}
void resolveIntent(Intent intent) {
// Parse the intent
String action = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)
|| NfcAdapter.ACTION_TECHNOLOGY_DISCOVERED.equals(action)) {
if (SHOW_OVER_LOCK_SCREEN) {
// A tag was just scanned so poke the user activity wake lock to keep
// the screen on a bit longer in the event that the activity has
// hidden the lock screen.
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
WakeLock wakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, TAG);
// This lock CANNOT be manually released in onStop() since that may
// cause a lock under run exception to be thrown when the timeout
// hits.
wakeLock.acquire(ACTIVITY_TIMEOUT_MS);
if (mReceiver == null) {
mReceiver = new ScreenOffReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(mReceiver, filter);
}
}
// When a tag is discovered we send it to the service to be save. We
// include a PendingIntent for the service to call back onto. This
// will cause this activity to be restarted with onNewIntent(). At
// that time we read it from the database and view it.
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
NdefMessage[] msgs;
if (rawMsgs != null && rawMsgs.length > 0) {
// stupid java, need to cast one-by-one
msgs = new NdefMessage[rawMsgs.length];
for (int i=0; i<rawMsgs.length; i++) {
msgs[i] = (NdefMessage) rawMsgs[i];
}
} else {
// Unknown tag type
byte[] empty = new byte[] {};
NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty, empty);
NdefMessage msg = new NdefMessage(new NdefRecord[] { record });
msgs = new NdefMessage[] { msg };
}
TagService.saveMessages(this, msgs, false, getPendingIntent());
// Setup the views
setTitle(R.string.title_scanned_tag);
mDate.setVisibility(View.GONE);
mStar.setChecked(false);
mStar.setEnabled(true);
// Play notification.
try {
AssetFileDescriptor afd = getResources().openRawResourceFd(
R.raw.discovered_tag_notification);
if (afd != null) {
MediaPlayer player = new MediaPlayer();
player.setDataSource(
afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
afd.close();
player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
mp.release();
}
});
player.prepare();
player.start();
}
} catch (IOException ex) {
Log.d(TAG, "Unable to play sound for tag discovery", ex);
} catch (IllegalArgumentException ex) {
Log.d(TAG, "Unable to play sound for tag discovery", ex);
} catch (SecurityException ex) {
Log.d(TAG, "Unable to play sound for tag discovery", ex);
}
} else if (Intent.ACTION_VIEW.equals(action)) {
// Setup the views
if (!intent.getBooleanExtra(EXTRA_KEEP_TITLE, false)) {
setTitle(R.string.title_existing_tag);
mDate.setVisibility(View.VISIBLE);
}
mStar.setVisibility(View.VISIBLE);
mStar.setEnabled(false); // it's reenabled when the async load completes
// Read the tag from the database asynchronously
mTagUri = intent.getData();
new LoadTagTask().execute(mTagUri);
} else {
Log.e(TAG, "Unknown intent " + intent);
finish();
return;
}
}
void buildTagViews(NdefMessage[] msgs) {
if (msgs == null || msgs.length == 0) {
return;
}
LayoutInflater inflater = LayoutInflater.from(this);
LinearLayout content = mTagContent;
// Clear out any old views in the content area, for example if you scan two tags in a row.
content.removeAllViews();
// Parse the first message in the list
//TODO figure out what to do when/if we support multiple messages per tag
ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msgs[0]);
// Build views for all of the sub records
List<ParsedNdefRecord> records = parsedMsg.getRecords();
final int size = records.size();
for (int i = 0 ; i < size ; i++) {
ParsedNdefRecord record = records.get(i);
content.addView(record.getView(this, inflater, content, i));
inflater.inflate(R.layout.tag_divider, content, true);
}
}
@Override
public void onNewIntent(Intent intent) {
setIntent(intent);
resolveIntent(intent);
}
@Override
public void setTitle(CharSequence title) {
mTitle.setText(title);
}
@Override
public void onClick(View view) {
if (view == mDeleteButton) {
if (mTagUri == null) {
finish();
} else {
// The tag came from the database, start a service to delete it
TagService.delete(this, mTagUri);
finish();
}
Toast.makeText(this, getResources().getString(R.string.tag_deleted), Toast.LENGTH_SHORT)
.show();
} else if (view == mDoneButton) {
finish();
} else if (view == mStar) {
if (mTagUri != null) {
TagService.setStar(this, mTagUri, mStar.isChecked());
}
}
}
interface ViewTagQuery {
final static String[] PROJECTION = new String[] {
NdefMessages.BYTES, // 0
NdefMessages.STARRED, // 1
NdefMessages.DATE, // 2
};
static final int COLUMN_BYTES = 0;
static final int COLUMN_STARRED = 1;
static final int COLUMN_DATE = 2;
}
/**
* Loads a tag from the database, parses it, and builds the views
*/
final class LoadTagTask extends AsyncTask<Uri, Void, Cursor> {
@Override
public Cursor doInBackground(Uri... args) {
Cursor cursor = getContentResolver().query(args[0], ViewTagQuery.PROJECTION,
null, null, null);
// Ensure the cursor loads its window
if (cursor != null) cursor.getCount();
return cursor;
}
@Override
public void onPostExecute(Cursor cursor) {
NdefMessage msg = null;
try {
if (cursor != null && cursor.moveToFirst()) {
msg = new NdefMessage(cursor.getBlob(ViewTagQuery.COLUMN_BYTES));
if (msg != null) {
mDate.setText(DateUtils.getRelativeTimeSpanString(TagViewer.this,
cursor.getLong(ViewTagQuery.COLUMN_DATE)));
mStar.setChecked(cursor.getInt(ViewTagQuery.COLUMN_STARRED) != 0);
mStar.setEnabled(true);
buildTagViews(new NdefMessage[] { msg });
}
}
} catch (FormatException e) {
Log.e(TAG, "invalid tag format", e);
} finally {
if (cursor != null) cursor.close();
}
}
}
}