Revert "Remove the My Tag feature."
This reverts commit fd860fde813f58808b389fbc4f9120a89a90b27e.
diff --git a/Android.mk b/Android.mk
index dd1d1b1..7fa60a0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -10,7 +10,7 @@
LOCAL_PACKAGE_NAME := Tag
-LOCAL_SDK_VERSION := current
+#LOCAL_SDK_VERSION := current
include $(BUILD_PACKAGE)
diff --git a/src/com/android/apps/tag/EditTagActivity.java b/src/com/android/apps/tag/EditTagActivity.java
new file mode 100644
index 0000000..50c4df9
--- /dev/null
+++ b/src/com/android/apps/tag/EditTagActivity.java
@@ -0,0 +1,250 @@
+/*
+ * 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.record.ImageRecord;
+import com.android.apps.tag.record.ParsedNdefRecord;
+import com.android.apps.tag.record.RecordEditInfo;
+import com.android.apps.tag.record.RecordEditInfo.EditCallbacks;
+import com.android.apps.tag.record.UriRecord;
+import com.android.apps.tag.record.VCardRecord;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Intent;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import java.util.ArrayList;
+import java.util.Set;
+
+/**
+ * A base {@link Activity} class for an editor of an {@link NdefMessage} tag.
+ *
+ * The core of the editing is done by various child {@link View}s that differ based on
+ * {@link ParsedNdefRecord} types. Each type of {@link ParsedNdefRecord} can build views to
+ * pick/select a new piece of content, or edit an existing content for the {@link NdefMessage}.
+ */
+public abstract class EditTagActivity extends Activity implements OnClickListener, EditCallbacks {
+
+ private static final String BUNDLE_KEY_OUTSTANDING_PICK = "outstanding-pick";
+ protected static final int DIALOG_ID_ADD_CONTENT = 0;
+
+ private static final Set<String> SUPPORTED_RECORD_TYPES = ImmutableSet.of(
+ ImageRecord.RECORD_TYPE,
+ UriRecord.RECORD_TYPE,
+ VCardRecord.RECORD_TYPE
+ );
+
+ /**
+ * Records contained in the current message being edited.
+ */
+ protected final ArrayList<RecordEditInfo> mRecords = Lists.newArrayList();
+
+ /**
+ * The container where the subviews for each record are housed.
+ */
+ private ViewGroup mContentRoot;
+
+ /**
+ * Info about an outstanding picking activity to add a new record.
+ */
+ private RecordEditInfo mRecordWithOutstandingPick;
+
+ private LayoutInflater mInflater;
+
+ @Override
+ protected void onCreate(Bundle savedState) {
+ super.onCreate(savedState);
+
+ if (savedState != null) {
+ mRecordWithOutstandingPick = savedState.getParcelable(BUNDLE_KEY_OUTSTANDING_PICK);
+ }
+ mInflater = LayoutInflater.from(this);
+ }
+
+ protected ViewGroup getContentRoot() {
+ if (mContentRoot == null) {
+ mContentRoot = (ViewGroup) findViewById(R.id.content_parent);
+ }
+ return mContentRoot;
+ }
+
+ /**
+ * @return The list of {@link ParsedNdefRecord} types that this editor supports. Subclasses
+ * may override to filter out specific types.
+ */
+ public Set<String> getSupportedTypes() {
+ return SUPPORTED_RECORD_TYPES;
+ }
+
+ /**
+ * Builds a {@link View} used as an item in a list when picking a new piece of content to add
+ * to the tag.
+ */
+ public View getAddView(ViewGroup parent, String type) {
+ if (ImageRecord.RECORD_TYPE.equals(type)) {
+ return ImageRecord.getAddView(this, mInflater, parent);
+ } else if (UriRecord.RECORD_TYPE.equals(type)) {
+ return UriRecord.getAddView(this, mInflater, parent);
+ } else if (VCardRecord.RECORD_TYPE.equals(type)) {
+ return VCardRecord.getAddView(this, mInflater, parent);
+ }
+ throw new IllegalArgumentException("Not a supported view type");
+ }
+
+ /**
+ * Builds a snapshot of current values as held in the internal state of this editor.
+ */
+ public ArrayList<NdefRecord> getValues() {
+ ArrayList<NdefRecord> result = new ArrayList<NdefRecord>(mRecords.size());
+ for (RecordEditInfo editInfo : mRecords) {
+ result.add(editInfo.getValue());
+ }
+ return result;
+ }
+
+ /**
+ * Builds a {@link View} used as an item in a list when editing content for a tag.
+ */
+ public void addRecord(RecordEditInfo editInfo) {
+ mRecords.add(Preconditions.checkNotNull(editInfo));
+ addViewForRecord(editInfo);
+ }
+
+ /**
+ * Adds a child editor view for a record.
+ */
+ public void addViewForRecord(RecordEditInfo editInfo) {
+ ViewGroup root = getContentRoot();
+ View editView = editInfo.getEditView(this, mInflater, root, this);
+ root.addView(mInflater.inflate(R.layout.tag_divider, root, false));
+ root.addView(editView);
+ }
+
+ protected void rebuildChildViews() {
+ ViewGroup root = getContentRoot();
+ root.removeAllViews();
+ for (RecordEditInfo editInfo : mRecords) {
+ addViewForRecord(editInfo);
+ }
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id, Bundle args) {
+ if (id == DIALOG_ID_ADD_CONTENT) {
+ return new TagContentSelector(this);
+ }
+ return super.onCreateDialog(id, args);
+ }
+
+ @Override
+ protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
+ super.onPrepareDialog(id, dialog, args);
+ if (dialog instanceof TagContentSelector) {
+ ((TagContentSelector) dialog).rebuildViews();
+ }
+ }
+
+ /**
+ * Displays a {@link Dialog} to select a new content type to add to the Tag.
+ */
+ protected void showAddContentDialog() {
+ showDialog(DIALOG_ID_ADD_CONTENT);
+ }
+
+ @Override
+ public void startPickForRecord(RecordEditInfo editInfo, Intent intent) {
+ mRecordWithOutstandingPick = editInfo;
+ startActivityForResult(intent, 0);
+ }
+
+ /**
+ * Handles a click to select and add a new content type.
+ */
+ public void onAddContentClick(View target) {
+ Object tag = target.getTag();
+ if ((tag == null) || !(tag instanceof RecordEditInfo)) {
+ return;
+ }
+
+ RecordEditInfo info = (RecordEditInfo) tag;
+ Intent pickIntent = info.getPickIntent();
+ if (pickIntent != null) {
+ startPickForRecord(info, pickIntent);
+ } else {
+ // Does not require an external Activity. Add the edit view directly.
+ addRecord(info);
+ }
+ }
+
+ @Override
+ public void deleteRecord(RecordEditInfo editInfo) {
+ mRecords.remove(editInfo);
+ rebuildChildViews();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if ((resultCode != RESULT_OK) || (data == null)) {
+ mRecordWithOutstandingPick = null;
+ return;
+ }
+ if (mRecordWithOutstandingPick == null) {
+ return;
+ }
+
+ // Handles results from another Activity that picked content to write to a tag.
+ RecordEditInfo recordInfo = mRecordWithOutstandingPick;
+ try {
+ recordInfo.handlePickResult(this, data);
+ } catch (IllegalArgumentException ex) {
+ if (mRecords.contains(recordInfo)) {
+ deleteRecord(recordInfo);
+ }
+ return;
+ }
+
+ if (mRecords.contains(recordInfo)) {
+ // Editing an existing record. Just rebuild everything.
+ rebuildChildViews();
+
+ } else {
+ // Adding a new record.
+ addRecord(recordInfo);
+ }
+ // TODO: handle errors in picking (e.g. the image is too big, etc).
+
+ mRecordWithOutstandingPick = null;
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ if (mRecordWithOutstandingPick != null) {
+ outState.putParcelable(BUNDLE_KEY_OUTSTANDING_PICK, mRecordWithOutstandingPick);
+ }
+ }
+}
diff --git a/src/com/android/apps/tag/MyTagActivity.java b/src/com/android/apps/tag/MyTagActivity.java
new file mode 100644
index 0000000..96bf0d2
--- /dev/null
+++ b/src/com/android/apps/tag/MyTagActivity.java
@@ -0,0 +1,233 @@
+/*
+ * 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.record.ParsedNdefRecord;
+import com.android.apps.tag.record.RecordEditInfo;
+import com.android.apps.tag.record.TextRecord;
+import com.android.apps.tag.record.UriRecord;
+import com.android.apps.tag.record.VCardRecord;
+import com.google.common.collect.Lists;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.CheckBox;
+import android.widget.EditText;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Editor {@link Activity} for the tag that can be programmed into the device.
+ */
+public class MyTagActivity extends EditTagActivity implements OnClickListener {
+
+ private static final String LOG_TAG = "TagEditor";
+
+ private EditText mTextView;
+ private CheckBox mEnabled;
+
+ /**
+ * Whether or not data was already parsed from an {@link Intent}. This happens when the user
+ * shares data via the My tag feature.
+ */
+ private boolean mParsedIntent = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.my_tag_activity);
+
+ findViewById(R.id.toggle_enabled_target).setOnClickListener(this);
+ findViewById(R.id.add_content_target).setOnClickListener(this);
+
+ mTextView = (EditText) findViewById(R.id.input_tag_text);
+ mEnabled = (CheckBox) findViewById(R.id.toggle_enabled_checkbox);
+
+ populateEditor();
+ }
+
+ private void populateEditor() {
+ NdefMessage localMessage = NfcAdapter.getDefaultAdapter().getLocalNdefMessage();
+
+ if (Intent.ACTION_SEND.equals(getIntent().getAction()) && !mParsedIntent) {
+ if (localMessage != null) {
+ // TODO: prompt user for confirmation about wiping their old tag.
+ }
+
+ if (buildFromIntent(getIntent())) {
+ return;
+ }
+
+ mParsedIntent = true;
+
+ } else if (localMessage == null) {
+ mEnabled.setChecked(false);
+ return;
+
+ } else {
+ // Locally stored message.
+ ParsedNdefMessage parsed = NdefMessageParser.parse(localMessage);
+ List<ParsedNdefRecord> records = parsed.getRecords();
+
+ // There is always a "Text" record for a My Tag.
+ if (records.size() < 1) {
+ Log.w(LOG_TAG, "Local record not in expected format");
+ return;
+ }
+ mEnabled.setChecked(true);
+ mTextView.setText(((TextRecord) records.get(0)).getText());
+
+ mRecords.clear();
+ for (int i = 1, len = records.size(); i < len; i++) {
+ RecordEditInfo editInfo = records.get(i).getEditInfo(this);
+ if (editInfo != null) {
+ addRecord(editInfo);
+ }
+ }
+ rebuildChildViews();
+ }
+ }
+
+ /**
+ * Populates the editor from extras in a given {@link Intent}
+ * @param intent the {@link Intent} to parse.
+ * @return whether or not the {@link Intent} could be handled.
+ */
+ private boolean buildFromIntent(final Intent intent) {
+ String type = intent.getType();
+
+ if ("text/plain".equals(type)) {
+ String title = getIntent().getStringExtra(Intent.EXTRA_SUBJECT);
+ mTextView.setText((title == null) ? "" : title);
+
+ String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
+ try {
+ URL parsed = new URL(text);
+
+ // Valid URL.
+ mTextView.setText("");
+ mRecords.add(new UriRecord.UriRecordEditInfo(text));
+ rebuildChildViews();
+
+ } catch (MalformedURLException ex) {
+ // Ignore. Just treat as plain text.
+ mTextView.setText((text == null) ? "" : text);
+ }
+
+ mEnabled.setChecked(true);
+ onSave();
+ return true;
+
+ } else if ("text/x-vcard".equals(type)) {
+ Uri stream = (Uri) getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
+ if (stream != null) {
+ RecordEditInfo editInfo = VCardRecord.editInfoForUri(stream);
+ if (editInfo != null) {
+ mRecords.add(editInfo);
+ rebuildChildViews();
+ }
+ }
+ }
+ // TODO: handle images.
+ return false;
+ }
+
+ /**
+ * Persists content to store.
+ */
+ private void onSave() {
+ String text = mTextView.getText().toString();
+ NfcAdapter nfc = NfcAdapter.getDefaultAdapter();
+
+ if (!mEnabled.isChecked()) {
+ nfc.setLocalNdefMessage(null);
+ return;
+ }
+
+ Locale locale = getResources().getConfiguration().locale;
+ ArrayList<NdefRecord> values = Lists.newArrayList(
+ TextRecord.newTextRecord(text, locale)
+ );
+
+ values.addAll(getValues());
+
+ Log.d(LOG_TAG, "Writing local NdefMessage from tag app....");
+ nfc.setLocalNdefMessage(new NdefMessage(values.toArray(new NdefRecord[values.size()])));
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ onSave();
+ }
+
+ @Override
+ public void onClick(View target) {
+ switch (target.getId()) {
+ case R.id.toggle_enabled_target:
+ boolean enabled = !mEnabled.isChecked();
+ mEnabled.setChecked(enabled);
+
+ // TODO: Persist to some store.
+ if (enabled) {
+ onSave();
+ } else {
+ NfcAdapter.getDefaultAdapter().setLocalNdefMessage(null);
+ }
+ break;
+
+ case R.id.add_content_target:
+ showAddContentDialog();
+ break;
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.help:
+ HelpUtils.openHelp(this);
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/src/com/android/apps/tag/TagBrowserActivity.java b/src/com/android/apps/tag/TagBrowserActivity.java
index d9bd04c..6df1567 100644
--- a/src/com/android/apps/tag/TagBrowserActivity.java
+++ b/src/com/android/apps/tag/TagBrowserActivity.java
@@ -60,6 +60,11 @@
.setContent(new Intent().setClass(this, TagList.class)
.putExtra(TagList.EXTRA_SHOW_STARRED_ONLY, true)));
+ tabHost.addTab(tabHost.newTabSpec("mytag")
+ .setIndicator(getText(R.string.tab_my_tag),
+ res.getDrawable(R.drawable.ic_tab_my_tag))
+ .setContent(new Intent().setClass(this, MyTagActivity.class)));
+
SharedPreferences preferences = getPreferences(Context.MODE_PRIVATE);
if (!preferences.getBoolean(PREF_KEY_SHOW_INTRO, false)) {
preferences.edit().putBoolean(PREF_KEY_SHOW_INTRO, true).apply();
diff --git a/src/com/android/apps/tag/TagContentSelector.java b/src/com/android/apps/tag/TagContentSelector.java
new file mode 100644
index 0000000..e1055a6
--- /dev/null
+++ b/src/com/android/apps/tag/TagContentSelector.java
@@ -0,0 +1,79 @@
+/*
+ * 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 android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.nfc.NdefRecord;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A {@link Dialog} that presents options to select data which can be written into a
+ * {@link NdefRecord} for an NFC tag.
+ */
+public class TagContentSelector extends AlertDialog
+ implements DialogInterface.OnClickListener, android.view.View.OnClickListener {
+
+ private final EditTagActivity mActivity;
+ private final ViewGroup mListRoot;
+
+ public TagContentSelector(EditTagActivity activity) {
+ super(activity);
+ mActivity = activity;
+
+ setTitle(activity.getResources().getString(R.string.select_type));
+
+ LayoutInflater inflater = LayoutInflater.from(activity);
+ ViewGroup root = (ViewGroup) inflater.inflate(R.layout.tag_content_selector, null);
+ mListRoot = (ViewGroup) root.findViewById(R.id.list);
+
+ rebuildViews();
+
+ setView(root);
+ setIcon(0);
+ setButton(
+ DialogInterface.BUTTON_POSITIVE,
+ activity.getString(android.R.string.cancel),
+ this);
+ }
+
+ public void rebuildViews() {
+ mListRoot.removeAllViews();
+ for (String type : mActivity.getSupportedTypes()) {
+ View selectItemView = mActivity.getAddView(mListRoot, type);
+ if (selectItemView != null) {
+ selectItemView.setOnClickListener(this);
+ mListRoot.addView(selectItemView);
+ }
+ }
+ }
+
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dismiss();
+ }
+
+ @Override
+ public void onClick(View target) {
+ mActivity.onAddContentClick(target);
+ dismiss();
+ }
+}
diff --git a/src/com/android/apps/tag/record/VCardRecord.java b/src/com/android/apps/tag/record/VCardRecord.java
index bfaa180..362a447 100644
--- a/src/com/android/apps/tag/record/VCardRecord.java
+++ b/src/com/android/apps/tag/record/VCardRecord.java
@@ -26,8 +26,8 @@
import com.android.vcard.VCardParser_V30;
import com.android.vcard.exception.VCardException;
import com.android.vcard.exception.VCardVersionException;
+import com.google.android.collect.Lists;
import com.google.common.base.Preconditions;
-import com.google.common.collect.Lists;
import android.app.Activity;
import android.content.ActivityNotFoundException;
diff --git a/src/com/android/vcard/VCardUtils.java b/src/com/android/vcard/VCardUtils.java
index c40eed7..a6eba12 100644
--- a/src/com/android/vcard/VCardUtils.java
+++ b/src/com/android/vcard/VCardUtils.java
@@ -422,22 +422,6 @@
return containsOnlyPrintableAscii(Arrays.asList(values));
}
- public static boolean isPrintableAscii(final char c) {
- final int asciiFirst = 0x20;
- final int asciiLast = 0x7E; // included
- return (asciiFirst <= c && c <= asciiLast) || c == '\r' || c == '\n';
- }
-
- public static boolean isPrintableAsciiOnly(final CharSequence str) {
- final int len = str.length();
- for (int i = 0; i < len; i++) {
- if (!isPrintableAscii(str.charAt(i))) {
- return false;
- }
- }
- return true;
- }
-
public static boolean containsOnlyPrintableAscii(final Collection<String> values) {
if (values == null) {
return true;
@@ -446,7 +430,7 @@
if (TextUtils.isEmpty(value)) {
continue;
}
- if (!isPrintableAsciiOnly(value)) {
+ if (!TextUtils.isPrintableAsciiOnly(value)) {
return false;
}
}