blob: 7f330cdde3eff3f827aeaf33db3df27369dce949 [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.provider.TagContract.NdefMessages;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.CharArrayBuffer;
import android.database.Cursor;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NfcAdapter;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.CheckBox;
import android.widget.CursorAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Displays the list of tags that can be set as "My tag", and allows the user to select the
* active tag that the device shares.
*/
public class MyTagList extends Activity implements OnItemClickListener, View.OnClickListener {
static final String TAG = "TagList";
private static final int REQUEST_EDIT = 0;
private static final int DIALOG_ID_SELECT_ACTIVE_TAG = 0;
private static final String BUNDLE_KEY_TAG_ID_IN_EDIT = "tag-edit";
private static final String PREF_KEY_ACTIVE_TAG = "active-my-tag";
static final String PREF_KEY_TAG_TO_WRITE = "tag-to-write";
private View mSelectActiveTagAnchor;
private View mActiveTagDetails;
private CheckBox mEnabled;
private ListView mList;
private TagAdapter mAdapter;
private long mActiveTagId;
private NdefMessage mActiveTag;
private WeakReference<SelectActiveTagDialog> mSelectActiveTagDialog;
private long mTagIdInEdit = -1;
private boolean mWriteSupport = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_tag_activity);
if (savedInstanceState != null) {
mTagIdInEdit = savedInstanceState.getLong(BUNDLE_KEY_TAG_ID_IN_EDIT, -1);
}
// Set up the check box to toggle My tag sharing.
mEnabled = (CheckBox) findViewById(R.id.toggle_enabled_checkbox);
mEnabled.setChecked(false); // Set after initial data load completes.
findViewById(R.id.toggle_enabled_target).setOnClickListener(this);
// Setup the active tag selector.
mActiveTagDetails = findViewById(R.id.active_tag_details);
mSelectActiveTagAnchor = findViewById(R.id.choose_my_tag);
findViewById(R.id.active_tag).setOnClickListener(this);
updateActiveTagView(null); // Filled in after initial data load.
mActiveTagId = getPreferences(Context.MODE_PRIVATE).getLong(PREF_KEY_ACTIVE_TAG, -1);
// Setup the list
mAdapter = new TagAdapter(this);
mList = (ListView) findViewById(android.R.id.list);
mList.setAdapter(mAdapter);
mList.setOnItemClickListener(this);
findViewById(R.id.add_tag).setOnClickListener(this);
// Kick off an async task to load the tags.
new TagLoaderTask().execute((Void[]) null);
// If we're not on a user build offer a back door for writing tags.
// The UX is horrible so we don't want to ship it but need it for testing.
if (!Build.TYPE.equalsIgnoreCase("user")) {
mWriteSupport = true;
registerForContextMenu(mList);
}
}
@Override
protected void onRestart() {
super.onRestart();
mTagIdInEdit = -1;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putLong(BUNDLE_KEY_TAG_ID_IN_EDIT, mTagIdInEdit);
}
@Override
protected void onDestroy() {
if (mAdapter != null) {
mAdapter.changeCursor(null);
}
super.onDestroy();
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (mWriteSupport) {
menu.add(0, 1, 0, "Write to next tag scanned");
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info;
try {
info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
} catch (ClassCastException e) {
Log.e(TAG, "bad menuInfo", e);
return false;
}
SharedPreferences prefs = getSharedPreferences("tags.pref", Context.MODE_PRIVATE);
prefs.edit().putLong(PREF_KEY_TAG_TO_WRITE, info.id).apply();
return true;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// TODO: use implicit Intent?
Intent intent = new Intent(this, EditTagActivity.class);
intent.setData(ContentUris.withAppendedId(NdefMessages.CONTENT_URI, id));
mTagIdInEdit = id;
startActivityForResult(intent, REQUEST_EDIT);
}
public void setEmptyView() {
// TODO: set empty view.
}
public interface TagQuery {
static final String[] PROJECTION = new String[] {
NdefMessages._ID, // 0
NdefMessages.DATE, // 1
NdefMessages.TITLE, // 2
NdefMessages.BYTES, // 3
};
static final int COLUMN_ID = 0;
static final int COLUMN_DATE = 1;
static final int COLUMN_TITLE = 2;
static final int COLUMN_BYTES = 3;
}
/**
* Asynchronously loads the tags info from the database.
*/
final class TagLoaderTask extends AsyncTask<Void, Void, Cursor> {
@Override
public Cursor doInBackground(Void... args) {
// Don't setup the empty view until after the first load
// so the empty text doesn't flash when first loading the
// activity.
mList.setEmptyView(null);
Cursor cursor = getContentResolver().query(
NdefMessages.CONTENT_URI,
TagQuery.PROJECTION,
NdefMessages.IS_MY_TAG + "=1",
null, NdefMessages.DATE + " DESC");
// Ensure the cursor executes and fills its window
if (cursor != null) cursor.getCount();
return cursor;
}
@Override
protected void onPostExecute(Cursor cursor) {
mAdapter.changeCursor(cursor);
if (cursor == null || cursor.getCount() == 0) {
setEmptyView();
} else {
// Find the active tag.
if (mActiveTagId != -1) {
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
if (mActiveTagId == cursor.getLong(TagQuery.COLUMN_ID)) {
selectActiveTag(cursor.getPosition());
// If there was an existing shared tag, we update the contents, since
// the active tag contents may have been changed. This also forces the
// active tag to be in sync with what the NfcAdapter.
if (NfcAdapter.getDefaultAdapter(MyTagList.this)
.getLocalNdefMessage() != null) {
enableSharing();
}
break;
}
}
}
}
SelectActiveTagDialog dialog = (mSelectActiveTagDialog == null)
? null : mSelectActiveTagDialog.get();
if (dialog != null) {
dialog.setData(cursor);
}
}
}
/**
* Struct to hold pointers to views in the list items to save time at view binding time.
*/
static final class ViewHolder {
public CharArrayBuffer titleBuffer;
public TextView mainLine;
}
/**
* Adapter to display the the My tag entries.
*/
public class TagAdapter extends CursorAdapter {
private final LayoutInflater mInflater;
public TagAdapter(Context context) {
super(context, null, false);
mInflater = LayoutInflater.from(context);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
ViewHolder holder = (ViewHolder) view.getTag();
CharArrayBuffer buf = holder.titleBuffer;
cursor.copyStringToBuffer(TagQuery.COLUMN_TITLE, buf);
holder.mainLine.setText(buf.data, 0, buf.sizeCopied);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.tag_list_item, null);
// Cache items for the view
ViewHolder holder = new ViewHolder();
holder.titleBuffer = new CharArrayBuffer(64);
holder.mainLine = (TextView) view.findViewById(R.id.title);
view.findViewById(R.id.date).setVisibility(View.GONE);
view.setTag(holder);
return view;
}
@Override
public void onContentChanged() {
// Kick off an async query to refresh the list
new TagLoaderTask().execute((Void[]) null);
}
}
@Override
public void onClick(View target) {
switch (target.getId()) {
case R.id.toggle_enabled_target:
boolean enabled = !mEnabled.isChecked();
if (enabled) {
if (mActiveTag != null) {
enableSharing();
return;
}
// TODO: just disable the checkbox when no tag is set
Toast.makeText(
this,
"You must select a tag to share first.",
Toast.LENGTH_SHORT).show();
}
disableSharing();
break;
case R.id.add_tag:
// TODO: use implicit intents.
Intent intent = new Intent(this, EditTagActivity.class);
startActivityForResult(intent, REQUEST_EDIT);
break;
case R.id.active_tag:
showDialog(DIALOG_ID_SELECT_ACTIVE_TAG);
break;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_EDIT && resultCode == RESULT_OK) {
NdefMessage msg = (NdefMessage) Preconditions.checkNotNull(
data.getParcelableExtra(EditTagActivity.EXTRA_RESULT_MSG));
if (mTagIdInEdit != -1) {
TagService.updateMyMessage(this, mTagIdInEdit, msg);
} else {
TagService.saveMyMessages(this, new NdefMessage[] { msg });
}
}
}
@Override
protected Dialog onCreateDialog(int id, Bundle args) {
if (id == DIALOG_ID_SELECT_ACTIVE_TAG) {
mSelectActiveTagDialog = new WeakReference<SelectActiveTagDialog>(
new SelectActiveTagDialog(this, mAdapter.getCursor()));
return mSelectActiveTagDialog.get();
}
return super.onCreateDialog(id, args);
}
/**
* Selects the tag to be used as the "My tag" shared tag.
*
* This does not necessarily persist the selection to the {@code NfcAdapter}. That must be done
* via {@link #enableSharing}. However, it will call {@link #disableSharing} if the tag
* is invalid.
*/
private void selectActiveTag(int position) {
Cursor cursor = mAdapter.getCursor();
if (cursor != null && cursor.moveToPosition(position)) {
mActiveTagId = cursor.getLong(TagQuery.COLUMN_ID);
try {
mActiveTag = new NdefMessage(cursor.getBlob(TagQuery.COLUMN_BYTES));
// Persist active tag info to preferences.
getPreferences(Context.MODE_PRIVATE)
.edit()
.putLong(PREF_KEY_ACTIVE_TAG, mActiveTagId)
.apply();
// Notify NFC adapter of the My tag contents.
updateActiveTagView(cursor.getString(TagQuery.COLUMN_TITLE));
} catch (FormatException e) {
// TODO: handle.
disableSharing();
}
} else {
updateActiveTagView(null);
disableSharing();
}
}
private void enableSharing() {
mEnabled.setChecked(true);
NfcAdapter.getDefaultAdapter(this).setLocalNdefMessage(
Preconditions.checkNotNull(mActiveTag));
}
private void disableSharing() {
mEnabled.setChecked(false);
NfcAdapter.getDefaultAdapter(this).setLocalNdefMessage(null);
}
private void updateActiveTagView(String title) {
if (title == null) {
mActiveTagDetails.setVisibility(View.GONE);
mSelectActiveTagAnchor.setVisibility(View.VISIBLE);
} else {
mActiveTagDetails.setVisibility(View.VISIBLE);
((TextView) mActiveTagDetails.findViewById(R.id.active_tag_title)).setText(title);
mSelectActiveTagAnchor.setVisibility(View.GONE);
}
}
class SelectActiveTagDialog extends AlertDialog
implements DialogInterface.OnClickListener, OnItemClickListener {
private final ArrayList<HashMap<String, String>> mData;
private final SimpleAdapter mSelectAdapter;
protected SelectActiveTagDialog(Context context, Cursor cursor) {
super(context);
setTitle(context.getResources().getString(R.string.choose_my_tag));
LayoutInflater inflater = LayoutInflater.from(context);
ListView list = new ListView(MyTagList.this);
mData = Lists.newArrayList();
mSelectAdapter = new SimpleAdapter(
context,
mData,
android.R.layout.simple_list_item_1,
new String[] { "title" },
new int[] { android.R.id.text1 });
list.setAdapter(mSelectAdapter);
list.setOnItemClickListener(this);
setView(list);
setIcon(0);
setButton(
DialogInterface.BUTTON_POSITIVE,
context.getString(android.R.string.cancel),
this);
setData(cursor);
}
public void setData(final Cursor cursor) {
if ((cursor == null) || (cursor.getCount() == 0)) {
cancel();
return;
}
mData.clear();
cursor.moveToPosition(-1);
while (cursor.moveToNext()) {
mData.add(new HashMap<String, String>() {{
put("title", cursor.getString(MyTagList.TagQuery.COLUMN_TITLE));
}});
}
mSelectAdapter.notifyDataSetChanged();
}
@Override
public void onClick(DialogInterface dialog, int which) {
cancel();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectActiveTag(position);
enableSharing();
cancel();
}
}
}