Project: android_packages_apps_Tag
/*
 * 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.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.base.Preconditions; 
import com.google.common.collect.ImmutableSet; 
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.DialogInterface.OnClickListener; 
import android.content.Intent; 
import android.database.CharArrayBuffer; 
import android.database.Cursor; 
import android.net.Uri; 
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.ContextThemeWrapper; 
import android.view.LayoutInflater; 
import android.view.MenuInflater; 
import android.view.MenuItem; 
import android.view.View; 
import android.view.ViewGroup; 
import android.widget.AdapterView; 
import android.widget.AdapterView.AdapterContextMenuInfo; 
import android.widget.AdapterView.OnItemClickListener; 
import android.widget.CheckBox; 
import android.widget.CursorAdapter; 
import android.widget.ImageView; 
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; 
import java.util.Set; 
 
/**
 * 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, 
                   TagService.SaveCallbacks, 
                   DialogInterface.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 int DIALOG_ID_ADD_NEW_TAG = 1
 
    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"
 
    static final String[] SUPPORTED_TYPES = new String[] { 
            VCardRecord.RECORD_TYPE, 
            UriRecord.RECORD_TYPE, 
            TextRecord.RECORD_TYPE, 
    }; 
 
    private View mSelectActiveTagAnchor; 
    private View mActiveTagDetails; 
    private CheckBox mEnabled; 
    private ListView mList; 
 
    private TagAdapter mAdapter; 
    private long mActiveTagId; 
    private Uri mTagBeingSaved; 
    private NdefMessage mActiveTag; 
 
    private WeakReference<SelectActiveTagDialog> mSelectActiveTagDialog; 
    private long mTagIdInEdit = -1
    private long mTagIdLongPressed; 
 
    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); 
 
        // 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); 
 
        // 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); 
 
        if (getIntent().hasExtra(EditTagActivity.EXTRA_RESULT_MSG)) { 
            NdefMessage msg = (NdefMessage) Preconditions.checkNotNull( 
                    getIntent().getParcelableExtra(EditTagActivity.EXTRA_RESULT_MSG)); 
            saveNewMessage(msg); 
        } 
    } 
 
    @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 onItemClick(AdapterView<?> parent, View view, int position, long id) { 
        editTag(id); 
    } 
 
    /**
     * Opens the tag editor for a particular tag. 
     */
 
    private void editTag(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) { 
            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 (mTagBeingSaved != null) { 
                    selectTagBeingSaved(mTagBeingSaved); 
 
                } else if (mActiveTagId != -1) { 
                    cursor.moveToPosition(-1); 
                    while (cursor.moveToNext()) { 
                        if (mActiveTagId == cursor.getLong(TagQuery.COLUMN_ID)) { 
                            selectActiveTag(cursor.getPosition()); 
                            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; 
        public ImageView activeIcon; 
    } 
 
    /**
     * Adapter to display the the My tag entries. 
     */
 
    public class TagAdapter extends CursorAdapter { 
        private final LayoutInflater mInflater; 
 
        public TagAdapter(Context context) { 
            super(context, nullfalse); 
            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); 
 
            boolean isActive = cursor.getLong(TagQuery.COLUMN_ID) == mActiveTagId; 
            holder.activeIcon.setVisibility(isActive ? View.VISIBLE : View.GONE); 
        } 
 
        @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); 
            holder.activeIcon = (ImageView) view.findViewById(R.id.active_tag_icon); 
            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) { 
                        enableSharingAndStoreTag(); 
                        return
                    } 
                    Toast.makeText( 
                            this
                            getResources().getString(R.string.no_tag_selected), 
                            Toast.LENGTH_SHORT).show(); 
                } 
 
                disableSharing(); 
                break
 
            case R.id.add_tag: 
                showDialog(DIALOG_ID_ADD_NEW_TAG); 
                break
 
            case R.id.active_tag: 
                if (mAdapter.getCursor() == null || mAdapter.getCursor().isClosed()) { 
                    // Hopefully shouldn't happen. 
                    return
                } 
 
                if (mAdapter.getCursor().getCount() == 0) { 
                    OnClickListener onAdd = new OnClickListener() { 
                        @Override 
                        public void onClick(DialogInterface dialog, int which) { 
                            if (which == AlertDialog.BUTTON_POSITIVE) { 
                                showDialog(DIALOG_ID_ADD_NEW_TAG); 
                            } 
                        } 
                    }; 
                    new AlertDialog.Builder(this
                            .setNegativeButton(android.R.string.cancel, null
                            .setPositiveButton(R.string.add_tag, onAdd) 
                            .setMessage(R.string.no_tags_created) 
                            .show(); 
                    return
                } 
                showDialog(DIALOG_ID_SELECT_ACTIVE_TAG); 
                break
        } 
    } 
 
    @Override 
    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) { 
        Cursor cursor = mAdapter.getCursor(); 
        if (cursor == null 
                || cursor.isClosed() 
                || !cursor.moveToPosition(((AdapterContextMenuInfo) info).position)) { 
            return
        } 
 
        menu.setHeaderTitle(cursor.getString(TagQuery.COLUMN_TITLE)); 
        long id = cursor.getLong(TagQuery.COLUMN_ID); 
        MenuInflater inflater = getMenuInflater(); 
        inflater.inflate(R.menu.my_tag_list_context_menu, menu); 
 
        // Prepare the menu for the item. 
        menu.findItem(R.id.set_as_active).setVisible(id != mActiveTagId); 
        mTagIdLongPressed = id; 
 
        if (mWriteSupport) { 
            menu.add(010"Write to tag"); 
        } 
    } 
 
    @Override 
    public boolean onContextItemSelected(MenuItem item) { 
        long id = mTagIdLongPressed; 
        switch (item.getItemId()) { 
            case R.id.delete: 
                deleteTag(id); 
                return true
 
            case R.id.set_as_active: 
                Cursor cursor = mAdapter.getCursor(); 
                if (cursor == null || cursor.isClosed()) { 
                    break
                } 
 
                for (int position = 0; cursor.moveToPosition(position); position++) { 
                    if (cursor.getLong(TagQuery.COLUMN_ID) == id) { 
                        selectActiveTag(position); 
                        return true
                    } 
                } 
                break
 
            case R.id.edit: 
                editTag(id); 
                return true
 
            case 1
                AdapterView.AdapterContextMenuInfo info; 
                try { 
                    info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); 
                } catch (ClassCastException e) { 
                    Log.e(TAG, "bad menuInfo", e); 
                    break
                } 
 
                Intent intent = new Intent(this, WriteTagActivity.class); 
                intent.putExtra("id", info.id); 
                startActivity(intent); 
                return true
        } 
        return false
    } 
 
    @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 { 
                saveNewMessage(msg); 
            } 
        } 
    } 
 
    private void saveNewMessage(NdefMessage msg) { 
        TagService.saveMyMessage(this, msg, this); 
    } 
 
    @Override 
    public void onSaveComplete(Uri newMsgUri) { 
        if (isFinishing()) { 
            // Callback came asynchronously and was after we finished - ignore. 
            return
        } 
        mTagBeingSaved = newMsgUri; 
        selectTagBeingSaved(newMsgUri); 
    } 
 
    @Override 
    protected Dialog onCreateDialog(int id, Bundle args) { 
        Context lightTheme = new ContextThemeWrapper(this, android.R.style.Theme_Light); 
        if (id == DIALOG_ID_SELECT_ACTIVE_TAG) { 
            SelectActiveTagDialog dialog = new SelectActiveTagDialog(lightTheme, 
                    mAdapter.getCursor()); 
            dialog.setInverseBackgroundForced(true); 
            mSelectActiveTagDialog = new WeakReference<SelectActiveTagDialog>(dialog); 
            return dialog; 
        } else if (id == DIALOG_ID_ADD_NEW_TAG) { 
            ContentSelectorAdapter adapter = new ContentSelectorAdapter(lightTheme, 
                    SUPPORTED_TYPES); 
            AlertDialog dialog = new AlertDialog.Builder(lightTheme) 
                    .setTitle(R.string.select_type) 
                    .setIcon(0
                    .setNegativeButton(android.R.string.cancel, this
                    .setAdapter(adapter, this
                    .create(); 
            adapter.setListView(dialog.getListView()); 
            dialog.setInverseBackgroundForced(true); 
            return dialog; 
        } 
        return super.onCreateDialog(id, args); 
    } 
 
    @Override 
    public void onClick(DialogInterface dialog, int which) { 
        if (which == DialogInterface.BUTTON_NEGATIVE) { 
            dialog.cancel(); 
        } else { 
            RecordEditInfo info = (RecordEditInfo) ((AlertDialog) dialog).getListView() 
                    .getAdapter().getItem(which); 
            Intent intent = new Intent(this, EditTagActivity.class); 
            intent.putExtra(EditTagActivity.EXTRA_NEW_RECORD_INFO, info); 
            startActivityForResult(intent, REQUEST_EDIT); 
        } 
    } 
 
    /**
     * 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 #enableSharingAndStoreTag()}. 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(); 
 
                updateActiveTagView(cursor.getString(TagQuery.COLUMN_TITLE)); 
                mAdapter.notifyDataSetChanged(); 
 
                // 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(this).getLocalNdefMessage() != null) { 
                    enableSharingAndStoreTag(); 
                } 
 
            } catch (FormatException e) { 
                // TODO: handle. 
                disableSharing(); 
            } 
        } else { 
            updateActiveTagView(null); 
            disableSharing(); 
        } 
        mTagBeingSaved = null
    } 
 
    /**
     * Selects the tag to be used as the "My tag" shared tag, if the specified URI is found. 
     * If the URI is not found, the next load will attempt to look for a matching tag to select. 
     * 
     * Commonly used for new tags that was just added to the database, and may not yet be 
     * reflected in the {@code Cursor}. 
     */
 
    private void selectTagBeingSaved(Uri uri) { 
        Cursor cursor = mAdapter.getCursor(); 
        if (cursor == null) { 
            return
        } 
        cursor.moveToPosition(-1); 
        while (cursor.moveToNext()) { 
            Uri tagUri = ContentUris.withAppendedId( 
                    NdefMessages.CONTENT_URI, 
                    cursor.getLong(TagQuery.COLUMN_ID)); 
            if (tagUri.equals(uri)) { 
                selectActiveTag(cursor.getPosition()); 
                return
            } 
        } 
    } 
 
    private void enableSharingAndStoreTag() { 
        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); 
        } 
    } 
 
    /**
     * Removes the tag from the "My tag" list. 
     */
 
    private void deleteTag(long id) { 
        if (id == mActiveTagId) { 
            selectActiveTag(-1); 
        } 
        TagService.delete(this, ContentUris.withAppendedId(NdefMessages.CONTENT_URI, id)); 
    } 
 
    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)); 
            ListView list = new ListView(context); 
 
            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); 
            enableSharingAndStoreTag(); 
            cancel(); 
        } 
    } 
}