2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.apps.tag;
19 import com.android.apps.tag.message.NdefMessageParser;
20 import com.android.apps.tag.message.ParsedNdefMessage;
21 import com.android.apps.tag.provider.TagContract.NdefMessages;
22 import com.android.apps.tag.record.ImageRecord;
23 import com.android.apps.tag.record.ParsedNdefRecord;
24 import com.android.apps.tag.record.RecordEditInfo;
25 import com.android.apps.tag.record.RecordEditInfo.EditCallbacks;
26 import com.android.apps.tag.record.UriRecord;
27 import com.android.apps.tag.record.VCardRecord;
28 import com.google.common.collect.ImmutableSet;
30 import android.app.Activity;
31 import android.content.Intent;
32 import android.database.Cursor;
33 import android.net.Uri;
34 import android.nfc.FormatException;
35 import android.nfc.NdefMessage;
36 import android.nfc.NdefRecord;
37 import android.os.AsyncTask;
38 import android.os.Bundle;
39 import android.util.Log;
40 import android.view.LayoutInflater;
41 import android.view.Menu;
42 import android.view.MenuItem;
43 import android.view.View;
44 import android.view.View.OnClickListener;
45 import android.view.ViewGroup;
47 import java.net.MalformedURLException;
49 import java.util.List;
53 * A base {@link Activity} class for an editor of an {@link NdefMessage} tag.
55 * The core of the editing is done by various a {@link View}s that differs based on
56 * {@link ParsedNdefRecord} types. Each type of {@link ParsedNdefRecord} can build a to
57 * pick/select a new piece of content, or edit an existing content for the {@link NdefMessage}.
59 public class EditTagActivity extends Activity implements OnClickListener, EditCallbacks {
61 private static final String LOG_TAG = "Tags";
63 protected static final String BUNDLE_KEY_OUTSTANDING_PICK = "outstanding-pick";
64 public static final String EXTRA_RESULT_MSG = "com.android.apps.tag.msg";
65 public static final String EXTRA_NEW_RECORD_INFO = "com.android.apps.tag.new_record";
67 protected static final Set<String> SUPPORTED_RECORD_TYPES = ImmutableSet.of(
68 VCardRecord.RECORD_TYPE,
73 * The underlying record in the tag being edited.
75 private RecordEditInfo mRecord;
78 * The container where the subviews for each record are housed.
80 private ViewGroup mContentRoot;
83 * Whether or not data was already parsed from an {@link Intent}. This happens when the user
84 * shares data via the My tag feature.
86 private boolean mParsedIntent = false;
88 private LayoutInflater mInflater;
91 protected void onCreate(Bundle savedState) {
92 super.onCreate(savedState);
94 setContentView(R.layout.edit_tag_activity);
95 setTitle(getResources().getString(R.string.edit_tag));
97 mInflater = LayoutInflater.from(this);
98 findViewById(R.id.save).setOnClickListener(this);
99 findViewById(R.id.cancel).setOnClickListener(this);
101 mContentRoot = (ViewGroup) findViewById(R.id.content_parent);
107 * @return The list of {@link ParsedNdefRecord} types that this editor supports. Subclasses
108 * may override to filter out specific types.
110 public Set<String> getSupportedTypes() {
111 return SUPPORTED_RECORD_TYPES;
115 * Builds a snapshot of current value as held in the internal state of this editor.
117 public NdefRecord getValue() {
118 return mRecord.getValue();
122 * Refreshes the UI with updated content from the record.
123 * Typically used when the records require an external {@link Activity} to edit.
125 public void refresh() {
126 ViewGroup root = mContentRoot;
127 View editView = mRecord.getEditView(this, mInflater, root, this);
128 root.removeAllViews();
129 root.addView(editView);
134 public void startPickForRecord(RecordEditInfo editInfo, Intent intent) {
135 startActivityForResult(intent, 0);
139 public void deleteRecord(RecordEditInfo editInfo) {
143 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
144 if ((resultCode != RESULT_OK) || (data == null)) {
148 // Handles results from another Activity that picked content to write to a tag.
150 mRecord.handlePickResult(this, data);
151 } catch (IllegalArgumentException ex) {
159 protected void onSaveInstanceState(Bundle outState) {
160 super.onSaveInstanceState(outState);
161 outState.putParcelable(BUNDLE_KEY_OUTSTANDING_PICK, mRecord);
164 interface GetTagQuery {
165 final static String[] PROJECTION = new String[] {
169 static final int COLUMN_BYTES = 0;
173 * Loads a tag from the database, parses it, and builds the views.
175 final class LoadTagTask extends AsyncTask<Uri, Void, Cursor> {
177 public Cursor doInBackground(Uri... args) {
178 Cursor cursor = getContentResolver().query(args[0], GetTagQuery.PROJECTION,
181 // Ensure the cursor loads its window.
182 if (cursor != null) {
189 public void onPostExecute(Cursor cursor) {
190 NdefMessage msg = null;
192 if (cursor != null && cursor.moveToFirst()) {
193 msg = new NdefMessage(cursor.getBlob(GetTagQuery.COLUMN_BYTES));
195 populateFromMessage(msg);
197 // TODO: do something more graceful.
201 } catch (FormatException e) {
202 Log.e(LOG_TAG, "Unable to parse tag for editing.", e);
204 if (cursor != null) {
211 protected void resolveIntent() {
212 Intent intent = getIntent();
214 if (Intent.ACTION_SEND.equals(intent.getAction()) && !mParsedIntent) {
215 if (buildFromSendIntent(intent)) {
219 mParsedIntent = true;
223 Uri uri = intent.getData();
225 // Edit existing tag.
226 new LoadTagTask().execute(uri);
228 RecordEditInfo newRecord = intent.getParcelableExtra(EXTRA_NEW_RECORD_INFO);
229 if (newRecord != null) {
230 initializeForNewTag(newRecord);
235 private void initializeForNewTag(RecordEditInfo editInfo) {
238 Intent pickIntent = editInfo.getPickIntent();
239 if (pickIntent != null) {
240 startPickForRecord(editInfo, pickIntent);
246 private void populateFromMessage(NdefMessage refMessage) {
247 // Locally stored message.
248 ParsedNdefMessage parsed = NdefMessageParser.parse(refMessage);
249 List<ParsedNdefRecord> records = parsed.getRecords();
251 // TODO: loosen this restriction.
252 // There is always a "Text" record for a My Tag.
253 if (records.size() != 1) {
254 Log.w(LOG_TAG, "Message not in expected format");
257 mRecord = records.get(0).getEditInfo(this);
262 * Populates the editor from extras in a given {@link Intent}
263 * @param intent the {@link Intent} to parse.
264 * @return whether or not the {@link Intent} could be handled.
266 private boolean buildFromSendIntent(final Intent intent) {
267 String type = intent.getType();
269 if ("text/plain".equals(type)) {
270 String text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
272 URL parsed = new URL(text);
275 mRecord = new UriRecord.UriRecordEditInfo(text);
279 } catch (MalformedURLException ex) {
283 } else if ("text/x-vcard".equals(type)) {
284 Uri stream = (Uri) getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
285 if (stream != null) {
286 RecordEditInfo editInfo = VCardRecord.editInfoForUri(stream);
287 if (editInfo != null) {
299 * Saves the content of the tag.
301 private void saveAndFinish() {
302 NdefMessage msg = new NdefMessage(new NdefRecord[] { mRecord.getValue() });
304 if (Intent.ACTION_SEND.equals(getIntent().getAction())) {
305 // If opening directly from a different application via ACTION_SEND, save the tag and
306 // open the MyTagList so they can enable it.
307 TagService.saveMyMessages(this, new NdefMessage[] { msg });
309 Intent openMyTags = new Intent(this, MyTagList.class);
310 startActivity(openMyTags);
314 Intent result = new Intent();
315 result.putExtra(EXTRA_RESULT_MSG, msg);
316 setResult(RESULT_OK, result);
322 public void onClick(View target) {
323 switch (target.getId()) {
334 public boolean onCreateOptionsMenu(Menu menu) {
335 getMenuInflater().inflate(R.menu.menu, menu);
340 public boolean onOptionsItemSelected(MenuItem item) {
341 switch (item.getItemId()) {
343 HelpUtils.openHelp(this);
347 return super.onOptionsItemSelected(item);