us.rader.tapset.nfc.ForegroundDispatchActivity.java Source code

Java tutorial

Introduction

Here is the source code for us.rader.tapset.nfc.ForegroundDispatchActivity.java

Source

/*
 * Copyright 2013 Kirk Rader
 * 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 us.rader.tapset.nfc;

import us.rader.tapset.R;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NdefMessage;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.widget.Toast;

/**
 * Boilerplate code for using {@link NfcAdapter} foreground dispatch
 * 
 * <h1>Rationale</h1>
 * 
 * <p>
 * Ordinarily, the Android OS will use the global intent-dispatching mechanism
 * to find an app to handle the contents of a NFC tag when it is detected.
 * Sometimes, however, a running app needs to usurp the notification that would
 * ordinarily be sent to some different app for a particular tag, e.g. when it
 * wishes to overwrite content that might have been written by some other app,
 * access the raw tag contents or underlying tag technology in some way etc.
 * </p>
 * 
 * <p>
 * In order for a running app to request that it be the one to be notified for a
 * given kind of {@link Tag} without regard to the usual global rules, it must
 * use the {@link NfcAdapter} foreground dispatch mechanism. This class provides
 * implementations of those specific {@link Activity} life-cycle methods
 * required to utilize foreground dispatch while deferring processing of the
 * contents of such tags to derived classes via <code>abstract</code> methods.
 * </p>
 * 
 * <h1>Design</h1>
 * 
 * <p>
 * The life-cycle of any {@link Activity} that uses foreground dispatch can be
 * summarized as:</code>
 * 
 * <ol>
 * 
 * <li>
 * <p>
 * Cache some helper objects in {@link #onCreate(Bundle)}:
 * 
 * <ul>
 * 
 * <li>
 * <p>
 * {@link NfcAdapter} singleton
 * </p>
 * </li>
 * 
 * <li>
 * <p>
 * {@link PendingIntent} wrapping a self-invoking
 * {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} intent
 * </p>
 * </li>
 * 
 * <li>
 * <p>
 * {@link IntentFilter} array to select the specific kinds of tags for which it
 * desires to be notified
 * </p>
 * </li>
 * 
 * </ul>
 * 
 * (See
 * {@link NfcAdapter#enableForegroundDispatch(Activity, PendingIntent, IntentFilter[], String[][])}
 * )
 * </p>
 * </li>
 * 
 * <li>
 * <p>
 * Enable foreground dispatch in {@link #onResume()}
 * </p>
 * </li>
 * 
 * <li>
 * <p>
 * Disable foreground dispatch in {@link #onPause()}
 * </p>
 * </li>
 * 
 * <li>
 * <p>
 * Process a {@link Tag} detected while foreground dispatch was enabled in
 * {@link #onNewIntent(Intent)}
 * </p>
 * </li>
 * 
 * </ol>
 * 
 * <h1>Implementation</h1>
 * 
 * <p>
 * In order to allow derived classes to process the {@link Tag} instances in
 * which it is interested that are detected while foreground dispatch is
 * enabled, this class declares the following <code>abstract</code> methods:
 * </p>
 * 
 * <dl>
 * 
 * <dt>{@link #createIntentFilters()}</dt>
 * <dd>
 * <p>
 * Populate the {@link IntentFilter} array that will be used while foreground
 * dispatch is enabled. This is the set of {@link Intent} meta-data that will
 * trigger an invocation of {@link #onNewIntent(Intent)}.
 * </p>
 * 
 * <p>
 * Note: this will be invoked by {@link #onCreate(Bundle)} and its value cached
 * for use by {@link #onResume()} rather than being repeatedly called each time
 * {@link #onResume()} is invoked.
 * </p>
 * </dd>
 * 
 * <dt>{@link #processTag(Intent)}</dt>
 * <dd>
 * <p>
 * Take whatever action is desired for an {@link Intent} passed to
 * {@link #onNewIntent(Intent)} while foreground dispatch is enabled. This will
 * be invoked in a worker thread, separate from the main UI.
 * </p>
 * </dd>
 * 
 * <dt>{@link #onTagProcessed(Object)}</dt>
 * <dd>
 * <p>
 * Take whatever action is desired based on the result of having called
 * {@link #processTag(Intent)} in a worker thread. This will be called in the
 * main UI thread.
 * </p>
 * </dd>
 * 
 * </dl>
 * 
 * <p>
 * {@link #processTag(Intent)} and {@link #onTagProcessed(Object)} are separate
 * methods so that they can be invoked in different threads. Care must be taken
 * to put all labor-intensive or blocking I/O operations in
 * {@link #processTag(Intent)} while putting only non-blocking operations that
 * directly affect the UI in {@link #onTagProcessed(Object)}
 * </p>
 * 
 * <a name="non_final"></a>
 * 
 * <h2>Non-Final Methods</h2>
 * 
 * <p>
 * As a rule of thumb, methods of <code>abstract</code> classes that are not,
 * themselves, <code>abstract</code> should generally be <code>final</code>.
 * This class violates that rule for {@link Activity} life-cycle methods like
 * {@link #onCreate(Bundle)}, {@link #onPause()}, {@link #onResume()} etc.
 * because of the high degree of likelihood that derived classes will also need
 * to override some or all of those particular methods. In all such cases, you
 * must be especially vigilant to always invoke <code>super</code>. [Too bad
 * Java doesn't support a mix-in style of programming, but I digress....]
 * </p>
 * 
 * @param <ContentType>
 *            The type returned by {@link #processTag(Intent)} and expected by
 *            {@link #onTagProcessed(Object)}; canonically, the type of data
 *            contained in the kind of {@link Tag} processed by the derived
 *            class (e.g. {@link NdefMessage}) but that is not actually a
 *            requirement enforced by this class
 * 
 * @see NfcAdapter#enableForegroundDispatch(Activity, PendingIntent,
 *      IntentFilter[], String[][])
 * @see #createIntentFilters()
 * @see #processTag(Intent)
 * @see #onTagProcessed(Object)
 * @see #onCreate(Bundle)
 * @see #onPause()
 * @see #onResume()
 * @see #onNewIntent(Intent)
 * @see NdefReaderActivity
 * @see NdefWriterActivity
 * 
 * @author Kirk
 */
public abstract class ForegroundDispatchActivity<ContentType> extends FragmentActivity {

    /**
     * Invoke {@link ForegroundDispatchActivity#processTag(Intent)} on a worker
     * thread, {@link ForegroundDispatchActivity#onTagProcessed(Object)} on the
     * UI thread
     */
    private class ProcessTagTask extends AsyncTask<Intent, Void, ContentType> {

        /**
         * Invoke {@link ForegroundDispatchActivity#processTag(Intent)}
         * 
         * @param intents
         *            <code>tags.length</code> must be exactly 1 and
         *            <code>tags[0]</code> must be the {@link Intent} to process
         * 
         * @return the value returned by
         *         {@link ForegroundDispatchActivity#processTag(Intent)} or
         *         <code><code>null</code> if an error occurs
         * 
         * @see android.os.AsyncTask#doInBackground(Tag...)
         * @see #onPostExecute(Object)
         * @see #onCancelled(Object)
         * @see ForegroundDispatchActivity#processTag(Intent)
         */
        @Override
        protected ContentType doInBackground(Intent... intents) {

            ContentType result = null;

            try {

                result = processTag(intents[0]);

            } catch (Exception e) {

                Log.e(getClass().getName(), "doInBackground", e); //$NON-NLS-1$
                toast(getString(R.string.error_processing_tag));

            }

            return result;

        }

        /**
         * Pass <code>null</code> to {@link #onCancelled(Object)}
         * 
         * This override is provided on behalf of devices running versions of
         * Android based on SDK 10 or earlier
         * 
         * @see android.os.AsyncTask#onCancelled()
         * @see #onCancelled(Object)
         */
        @Override
        protected void onCancelled() {

            onCancelled(null);

        }

        /**
         * Pass <code>result</code> to
         * {@link ForegroundDispatchActivity#onTagProcessed(Object)}
         * 
         * Note that the overload of <code>onCancelled</code> that takes a
         * parameter was added in SDK 11, while this class strives to be
         * backwards-compatible to SDK 10, hence the override of
         * {@link #onCancelled()} that calls this one
         * 
         * @param result
         *            the value to pass to
         *            {@link ForegroundDispatchActivity#onTagProcessed(Object)};
         *            may (probably will) be <code>null</code>
         * 
         * @see android.os.AsyncTask#onCancelled(java.lang.Object)
         * @see #onCancelled()
         */
        @Override
        protected void onCancelled(ContentType result) {

            try {

                onTagProcessed(result);

            } catch (Exception e) {

                Log.e(getClass().getName(), "onCancelled", e); //$NON-NLS-1$

            }
        }

        /**
         * Pass <code>result</code> to
         * {@link ForegroundDispatchActivity#onTagProcessed(Object)}
         * 
         * @param result
         *            value returned by
         *            {@link ForegroundDispatchActivity#processTag(Intent)}
         * 
         * @see android.os.AsyncTask#onPostExecute(java.lang.Object)
         */
        @Override
        protected void onPostExecute(ContentType result) {

            try {

                onTagProcessed(result);

            } catch (Exception e) {

                Log.e(getClass().getName(), "onPostExecute", e); //$NON-NLS-1$
                toast(getString(R.string.error_processing_tag));

            }
        }

    }

    /**
     * Cached {@link NfcAdapter}
     * 
     * @see #onPause()
     * @see #onResume()
     */
    private NfcAdapter adapter;

    /**
     * Cached {@link IntentFilter} array
     * 
     * @see #onResume()
     */
    private IntentFilter[] filters;

    /**
     * Cached {@link PendingIntent}
     * 
     * @see #onResume()
     */
    private PendingIntent pendingIntent;

    /**
     * Request code to use when enabling foreground dispatch
     * 
     * @see #onResume()
     */
    private int requestCode;

    /**
     * Initialize {@link #requestCode} to the given value
     * 
     * @param requestCode
     *            value for {@link #requestCode}
     * 
     * @see #onResume()
     */
    protected ForegroundDispatchActivity(int requestCode) {

        this.requestCode = requestCode;

    }

    /**
     * Create the {@link IntentFilter} array to use when foreground dispatch is
     * enabled
     * 
     * @return {@link IntentFilter} that includes filters(s) for the tags and
     *         technologies supported by the derived class
     * 
     * @see #onCreate(Bundle)
     * @see #onResume()
     */
    protected abstract IntentFilter[] createIntentFilters();

    /**
     * Initialize the data structures used in conjunction with foreground
     * dispatch
     * 
     * @param savedInstanceState
     *            saved state or <code>null</code>
     * 
     * @see FragmentActivity#onCreate(Bundle)
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        adapter = NfcAdapter.getDefaultAdapter(this);
        filters = createIntentFilters();
        Intent intent = new Intent(this, getClass());
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        pendingIntent = PendingIntent.getActivity(this, requestCode, intent, 0);

    }

    /**
     * Handle response to foreground dispatch request
     * 
     * @param intent
     *            the {@link Intent} containing the response
     * 
     * @see FragmentActivity#onNewIntent(Intent)
     */
    @Override
    protected void onNewIntent(Intent intent) {

        super.onNewIntent(intent);
        new ProcessTagTask().execute(intent);

    }

    /**
     * Disable foreground dispatch
     * 
     * @see android.support.v4.app.FragmentActivity#onPause()
     * @see #onResume()
     */
    @Override
    protected void onPause() {

        super.onPause();
        adapter.disableForegroundDispatch(this);

    }

    /**
     * Enable foreground dispatch
     * 
     * @see android.support.v4.app.FragmentActivity#onResume()
     * @see #onPause()
     * @see #onNewIntent(Intent)
     */
    @Override
    protected void onResume() {

        super.onResume();
        adapter.enableForegroundDispatch(this, pendingIntent, filters, null);

    }

    /**
     * Handle the result of having called {@link #processTag(Intent)}
     * 
     * This method can rely on, and must take account of being called in the UI
     * thread.
     * 
     * @param result
     *            the value returned from {@link #processTag(Intent)} or
     *            <code>null</code> if the latter threw an exception
     * 
     * @see #processTag(Intent)
     */
    protected abstract void onTagProcessed(ContentType result);

    /**
     * Handle a {@link Tag} detected while foreground dispatch was enabled
     * 
     * This method can rely on, and must take account of being called in a
     * worker thread. The value returned here will be passed on to
     * {@link #onTagProcessed(Object)} in the UI thread.
     * 
     * @param intent
     *            the {@link Intent} passed to {@link #onNewIntent(Intent)}
     *            while foreground dispatch was enabled
     * 
     * @return value to pass to {@link #onTagProcessed(Object)}
     * 
     * @see #onTagProcessed(Object)
     */
    protected abstract ContentType processTag(Intent intent);

    /**
     * Convenience method to display a {@link Toast} from any thread
     * 
     * This uses {@link #runOnUiThread(Runnable)} so that it can be called from
     * any thread, e.g. the one running
     * {@link ProcessTagTask#doInBackground(Intent...)}
     * 
     * @param message
     *            the message to display
     */
    protected final void toast(final String message) {

        runOnUiThread(new Runnable() {

            @Override
            public void run() {

                Toast.makeText(ForegroundDispatchActivity.this, message, Toast.LENGTH_SHORT).show();

            }

        });
    }

}