am 8188f826: Merge "Multiple fixes to CMAS app." into jb-mr1-dev

* commit '8188f826ab8573d3c08566c61d0708f465115a78':
  Multiple fixes to CMAS app.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3294be5..9b60968 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -49,7 +49,7 @@
         <activity android:name="CellBroadcastListActivity"
                   android:label="@string/app_label"
                   android:configChanges="orientation|keyboardHidden"
-                  android:launchMode="singleTop">
+                  android:launchMode="singleTask">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
 <!-- Uncomment this category to show the Cell Broadcasts launcher icon.
@@ -65,14 +65,15 @@
             </intent-filter>
         </activity>
 
+        <!-- Settings opened by ListActivity menu, Settings app link or opt-out dialog. -->
         <activity android:name="CellBroadcastSettings"
                   android:label="@string/sms_cb_settings"
+                  android:launchMode="singleTask"
                   android:exported="true" />
 
         <activity android:name="CellBroadcastAlertDialog"
-                  android:excludeFromRecents="true"
                   android:theme="@android:style/Theme.Holo.Dialog"
-                  android:launchMode="singleInstance"
+                  android:launchMode="singleTask"
                   android:exported="false"
                   android:configChanges="orientation|keyboardHidden|keyboard|navigation" />
 
@@ -80,10 +81,14 @@
         <activity android:name="CellBroadcastAlertFullScreen"
                   android:excludeFromRecents="true"
                   android:theme="@style/AlertFullScreenTheme"
-                  android:launchMode="singleInstance"
+                  android:launchMode="singleTask"
                   android:exported="false"
                   android:configChanges="orientation|keyboardHidden|keyboard|navigation" />
 
+        <!-- Container activity for CMAS opt-in/opt-out dialog. -->
+        <activity android:name="CellBroadcastOptOutActivity"
+                  android:exported="false" />
+
         <!-- Require sender permissions to prevent SMS spoofing -->
         <receiver android:name="PrivilegedCellBroadcastReceiver"
             android:permission="android.permission.BROADCAST_SMS">
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e3714ba..0a9b387 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -213,6 +213,18 @@
     <!-- Non-emergency broadcast notification title for multiple unread alerts. -->
     <string name="notification_multiple_title">New alerts</string>
 
+    <!-- Show CMAS opt-out dialog on first non-Presidential alert. [CHAR LIMIT=100] -->
+    <string name="show_cmas_opt_out_summary">Show an opt-out dialog after displaying the first CMAS alert (other than Presidential Alert).</string>
+    <!-- Show CMAS opt-out dialog on first non-Presidential alert. [CHAR LIMIT=40] -->
+    <string name="show_cmas_opt_out_title">Show opt-out dialog</string>
+
+    <!-- CMAS opt-out dialog message. [CHAR LIMIT=160] -->
+    <string name="cmas_opt_out_dialog_text">You are currently receiving Emergency Alerts. Would you like to continue receiving Emergency Alerts?</string>
+    <!-- Text for positive button in CMAS opt-out dialog. [CHAR LIMIT=25] -->
+    <string name="cmas_opt_out_button_yes">Yes</string>
+    <!-- Text for negative button in CMAS opt-out dialog. [CHAR LIMIT=25] -->
+    <string name="cmas_opt_out_button_no">No</string>
+
     <!-- Entries listed in the ListPreference for allowed alert durations. [CHAR LIMIT=30] -->
     <string-array name="alert_sound_duration_entries">
       <item>2 seconds</item>
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 5c29f08..340830a 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -96,6 +96,11 @@
                             android:summary="@string/enable_cmas_test_alerts_summary"
                             android:title="@string/enable_cmas_test_alerts_title" />
 
+        <CheckBoxPreference android:defaultValue="true"
+                            android:key="show_cmas_opt_out_dialog"
+                            android:summary="@string/show_cmas_opt_out_summary"
+                            android:title="@string/show_cmas_opt_out_title" />
+
     </PreferenceCategory>
 
 </PreferenceScreen>
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
index 2b9dabc..bc44ed1 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
@@ -27,7 +27,6 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
-import android.os.PowerManager;
 import android.os.Vibrator;
 import android.speech.tts.TextToSpeech;
 import android.telephony.PhoneStateListener;
@@ -76,9 +75,6 @@
     private static final long[] sVibratePattern = { 0, 2000, 500, 1000, 500, 1000, 500,
             2000, 500, 1000, 500, 1000};
 
-    /** CPU wake lock while playing audio. */
-    private PowerManager.WakeLock mWakeLock;
-
     private static final int STATE_IDLE = 0;
     private static final int STATE_ALERTING = 1;
     private static final int STATE_PAUSING = 2;
@@ -199,11 +195,6 @@
 
     @Override
     public void onCreate() {
-        // acquire CPU wake lock while playing audio
-        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
-        mWakeLock.acquire();
-
         mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
         // Listen for incoming calls to kill the alarm.
@@ -228,8 +219,8 @@
                 Log.e(TAG, "exception trying to shutdown text-to-speech");
             }
         }
-        // release CPU wake lock
-        mWakeLock.release();
+        // release CPU wake lock acquired by CellBroadcastAlertService
+        CellBroadcastAlertWakeLock.releaseCpuLock();
     }
 
     @Override
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
index 3be8439..f250928 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertDialog.java
@@ -45,7 +45,7 @@
 
         // Listen for the screen turning off so that when the screen comes back
         // on, the user does not need to unlock the phone to dismiss the alert.
-        if (mMessage.isEmergencyAlertMessage()) {
+        if (CellBroadcastConfigService.isEmergencyAlertMessage(getLatestMessage())) {
             mScreenOffReceiver = new ScreenOffReceiver();
             registerReceiver(mScreenOffReceiver,
                     new IntentFilter(Intent.ACTION_SCREEN_OFF));
@@ -74,8 +74,8 @@
     private void handleScreenOff() {
         // Launch the full screen activity but do not turn the screen on.
         Intent i = new Intent(this, CellBroadcastAlertFullScreen.class);
-        i.putExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, mMessage);
-        i.putExtra(SCREEN_OFF, true);
+        i.putParcelableArrayListExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, mMessageList);
+        i.putExtra(SCREEN_OFF_EXTRA, true);
         startActivity(i);
         finish();
     }
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java
index 9940426..cf6d7e5 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java
@@ -17,15 +17,21 @@
 package com.android.cellbroadcastreceiver;
 
 import android.app.Activity;
+import android.app.KeyguardManager;
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.preference.PreferenceManager;
 import android.provider.Telephony;
 import android.telephony.CellBroadcastMessage;
+import android.telephony.SmsCbCmasInfo;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -35,24 +41,31 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
 /**
  * Full-screen emergency alert with flashing warning icon.
  * Alert audio and text-to-speech handled by {@link CellBroadcastAlertAudio}.
  * Keyguard handling based on {@code AlarmAlertFullScreen} class from DeskClock app.
  */
 public class CellBroadcastAlertFullScreen extends Activity {
+    private static final String TAG = "CellBroadcastAlertFullScreen";
 
     /**
      * Intent extra for full screen alert launched from dialog subclass as a result of the
      * screen turning off.
      */
-    static final String SCREEN_OFF = "screen_off";
+    static final String SCREEN_OFF_EXTRA = "screen_off";
 
-    /** Whether to show the flashing warning icon. */
-    private boolean mIsEmergencyAlert;
+    /** Intent extra for non-emergency alerts sent when user selects the notification. */
+    static final String FROM_NOTIFICATION_EXTRA = "from_notification";
 
-    /** The cell broadcast message to display. */
-    CellBroadcastMessage mMessage;
+    /** List of cell broadcast messages to display (oldest to newest). */
+    ArrayList<CellBroadcastMessage> mMessageList;
+
+    /** Whether a CMAS alert other than Presidential Alert was displayed. */
+    private boolean mShowOptOutDialog;
 
     /** Length of time for the warning icon to be visible. */
     private static final int WARNING_ICON_ON_DURATION_MSEC = 800;
@@ -60,37 +73,172 @@
     /** Length of time for the warning icon to be off. */
     private static final int WARNING_ICON_OFF_DURATION_MSEC = 800;
 
-    /** Warning icon state. false = visible, true = off */
-    private boolean mIconAnimationState;
+    /** Length of time to keep the screen turned on. */
+    private static final int KEEP_SCREEN_ON_DURATION_MSEC = 60000;
 
-    /** Stop animating icon after {@link #onStop()} is called. */
-    private boolean mStopAnimation;
+    /** Animation handler for the flashing warning icon (emergency alerts only). */
+    private final AnimationHandler mAnimationHandler = new AnimationHandler();
 
-    /** The warning icon Drawable. */
-    private Drawable mWarningIcon;
+    /** Handler to add and remove screen on flags for emergency alerts. */
+    private final ScreenOffHandler mScreenOffHandler = new ScreenOffHandler();
 
-    /** The View containing the warning icon. */
-    private ImageView mWarningIconView;
+    /**
+     * Animation handler for the flashing warning icon (emergency alerts only).
+     */
+    private class AnimationHandler extends Handler {
+        /** Latest {@code message.what} value for detecting old messages. */
+        private final AtomicInteger mCount = new AtomicInteger();
 
-    /** Icon animation handler for flashing warning alerts. */
-    private final Handler mAnimationHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            if (mIconAnimationState) {
-                mWarningIconView.setImageAlpha(255);
-                if (!mStopAnimation) {
-                    mAnimationHandler.sendEmptyMessageDelayed(0, WARNING_ICON_ON_DURATION_MSEC);
-                }
-            } else {
-                mWarningIconView.setImageAlpha(0);
-                if (!mStopAnimation) {
-                    mAnimationHandler.sendEmptyMessageDelayed(0, WARNING_ICON_OFF_DURATION_MSEC);
-                }
+        /** Warning icon state: visible == true, hidden == false. */
+        private boolean mWarningIconVisible;
+
+        /** The warning icon Drawable. */
+        private Drawable mWarningIcon;
+
+        /** The View containing the warning icon. */
+        private ImageView mWarningIconView;
+
+        /** Package local constructor (called from outer class). */
+        AnimationHandler() {}
+
+        /** Start the warning icon animation. */
+        void startIconAnimation() {
+            if (!initDrawableAndImageView()) {
+                return;     // init failure
             }
-            mIconAnimationState = !mIconAnimationState;
+            mWarningIconVisible = true;
+            mWarningIconView.setVisibility(View.VISIBLE);
+            updateIconState();
+            queueAnimateMessage();
+        }
+
+        /** Stop the warning icon animation. */
+        void stopIconAnimation() {
+            // Increment the counter so the handler will ignore the next message.
+            mCount.incrementAndGet();
+            if (mWarningIconView != null) {
+                mWarningIconView.setVisibility(View.GONE);
+            }
+        }
+
+        /** Update the visibility of the warning icon. */
+        private void updateIconState() {
+            mWarningIconView.setImageAlpha(mWarningIconVisible ? 255 : 0);
             mWarningIconView.invalidateDrawable(mWarningIcon);
         }
-    };
+
+        /** Queue a message to animate the warning icon. */
+        private void queueAnimateMessage() {
+            int msgWhat = mCount.incrementAndGet();
+            sendEmptyMessageDelayed(msgWhat, mWarningIconVisible ? WARNING_ICON_ON_DURATION_MSEC
+                    : WARNING_ICON_OFF_DURATION_MSEC);
+            // Log.d(TAG, "queued animation message id = " + msgWhat);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == mCount.get()) {
+                mWarningIconVisible = !mWarningIconVisible;
+                updateIconState();
+                queueAnimateMessage();
+            }
+        }
+
+        /**
+         * Initialize the Drawable and ImageView fields.
+         * @return true if successful; false if any field failed to initialize
+         */
+        private boolean initDrawableAndImageView() {
+            if (mWarningIcon == null) {
+                try {
+                    mWarningIcon = getResources().getDrawable(R.drawable.ic_warning_large);
+                } catch (Resources.NotFoundException e) {
+                    Log.e(TAG, "warning icon resource not found", e);
+                    return false;
+                }
+            }
+            if (mWarningIconView == null) {
+                mWarningIconView = (ImageView) findViewById(R.id.icon);
+                if (mWarningIconView != null) {
+                    mWarningIconView.setImageDrawable(mWarningIcon);
+                } else {
+                    Log.e(TAG, "failed to get ImageView for warning icon");
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Handler to add {@code FLAG_KEEP_SCREEN_ON} for emergency alerts. After a short delay,
+     * remove the flag so the screen can turn off to conserve the battery.
+     */
+    private class ScreenOffHandler extends Handler {
+        /** Latest {@code message.what} value for detecting old messages. */
+        private final AtomicInteger mCount = new AtomicInteger();
+
+        /** Package local constructor (called from outer class). */
+        ScreenOffHandler() {}
+
+        /** Add screen on window flags and queue a delayed message to remove them later. */
+        void startScreenOnTimer() {
+            addWindowFlags();
+            int msgWhat = mCount.incrementAndGet();
+            removeMessages(msgWhat - 1);    // Remove previous message, if any.
+            sendEmptyMessageDelayed(msgWhat, KEEP_SCREEN_ON_DURATION_MSEC);
+            Log.d(TAG, "added FLAG_KEEP_SCREEN_ON, queued screen off message id " + msgWhat);
+        }
+
+        /** Remove the screen on window flags and any queued screen off message. */
+        void stopScreenOnTimer() {
+            removeMessages(mCount.get());
+            clearWindowFlags();
+        }
+
+        /** Set the screen on window flags. */
+        private void addWindowFlags() {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        }
+
+        /** Clear the screen on window flags. */
+        private void clearWindowFlags() {
+            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            int msgWhat = msg.what;
+            if (msgWhat == mCount.get()) {
+                clearWindowFlags();
+                Log.d(TAG, "removed FLAG_KEEP_SCREEN_ON with id " + msgWhat);
+            } else {
+                Log.e(TAG, "discarding screen off message with id " + msgWhat);
+            }
+        }
+    }
+
+    /** Returns the currently displayed message. */
+    CellBroadcastMessage getLatestMessage() {
+        int index = mMessageList.size() - 1;
+        if (index >= 0) {
+            return mMessageList.get(index);
+        } else {
+            return null;
+        }
+    }
+
+    /** Removes and returns the currently displayed message. */
+    private CellBroadcastMessage removeLatestMessage() {
+        int index = mMessageList.size() - 1;
+        if (index >= 0) {
+            return mMessageList.remove(index);
+        } else {
+            return null;
+        }
+    }
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -106,72 +254,131 @@
                 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                 | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
 
-        // Turn on the screen unless we're being launched from the dialog subclass as a result of
-        // the screen turning off.
-        if (!getIntent().getBooleanExtra(SCREEN_OFF, false)) {
-            win.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
-                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-        }
-
-        // Save message for passing from dialog to fullscreen activity, and for marking read.
-        mMessage = getIntent().getParcelableExtra(
-                CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA);
-
-        updateLayout(mMessage);
-    }
-
-    protected int getLayoutResId() {
-        return R.layout.cell_broadcast_alert_fullscreen;
-    }
-
-    private void updateLayout(CellBroadcastMessage message) {
+        // Initialize the view.
         LayoutInflater inflater = LayoutInflater.from(this);
-
         setContentView(inflater.inflate(getLayoutResId(), null));
 
-        /* Initialize dialog text from alert message. */
-        int titleId = CellBroadcastResources.getDialogTitleResource(message);
-        setTitle(titleId);
-        ((TextView) findViewById(R.id.alertTitle)).setText(titleId);
-        ((TextView) findViewById(R.id.message)).setText(message.getMessageBody());
-
-        /* dismiss button: close notification */
         findViewById(R.id.dismissButton).setOnClickListener(
                 new Button.OnClickListener() {
+                    @Override
                     public void onClick(View v) {
                         dismiss();
                     }
                 });
 
-        mIsEmergencyAlert = message.isPublicAlertMessage() || CellBroadcastConfigService
-                .isOperatorDefinedEmergencyId(message.getServiceCategory());
+        // Get message list from saved Bundle or from Intent.
+        if (savedInstanceState != null) {
+            Log.d(TAG, "onCreate getting message list from saved instance state");
+            mMessageList = savedInstanceState.getParcelableArrayList(
+                    CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA);
+        } else {
+            Log.d(TAG, "onCreate getting message list from intent");
+            Intent intent = getIntent();
+            mMessageList = intent.getParcelableArrayListExtra(
+                    CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA);
 
-        if (mIsEmergencyAlert) {
-            mWarningIcon = getResources().getDrawable(R.drawable.ic_warning_large);
-            mWarningIconView = (ImageView) findViewById(R.id.icon);
-            if (mWarningIconView != null) {
-                mWarningIconView.setImageDrawable(mWarningIcon);
-            }
-
-            // Dismiss the notification that brought us here
-            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE))
-                    .cancel((int) message.getDeliveryTime());
+            // If we were started from a notification, dismiss it.
+            clearNotification(intent);
         }
+
+        if (mMessageList != null) {
+            Log.d(TAG, "onCreate loaded message list of size " + mMessageList.size());
+        } else {
+            Log.e(TAG, "onCreate failed to get message list from saved Bundle");
+            finish();
+        }
+
+        // For emergency alerts, keep screen on so the user can read it, unless this is a
+        // full screen alert created by CellBroadcastAlertDialog when the screen turned off.
+        CellBroadcastMessage message = getLatestMessage();
+        if (CellBroadcastConfigService.isEmergencyAlertMessage(message) &&
+                (savedInstanceState != null ||
+                        !getIntent().getBooleanExtra(SCREEN_OFF_EXTRA, false))) {
+            Log.d(TAG, "onCreate setting screen on timer for emergency alert");
+            mScreenOffHandler.startScreenOnTimer();
+        }
+
+        updateAlertText(message);
+    }
+
+    /**
+     * Called by {@link CellBroadcastAlertService} to add a new alert to the stack.
+     * @param intent The new intent containing one or more {@link CellBroadcastMessage}s.
+     */
+    @Override
+    protected void onNewIntent(Intent intent) {
+        ArrayList<CellBroadcastMessage> newMessageList = intent.getParcelableArrayListExtra(
+                CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA);
+        if (newMessageList != null) {
+            Log.d(TAG, "onNewIntent called with message list of size " + newMessageList.size());
+            mMessageList.addAll(newMessageList);
+            updateAlertText(getLatestMessage());
+            // If the new intent was sent from a notification, dismiss it.
+            clearNotification(intent);
+        } else {
+            Log.e(TAG, "onNewIntent called without SMS_CB_MESSAGE_EXTRA, ignoring");
+        }
+    }
+
+    /** Try to cancel any notification that may have started this activity. */
+    private void clearNotification(Intent intent) {
+        if (intent.getBooleanExtra(FROM_NOTIFICATION_EXTRA, false)) {
+            Log.d(TAG, "Dismissing notification");
+            NotificationManager notificationManager =
+                    (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+            notificationManager.cancel(CellBroadcastAlertService.NOTIFICATION_ID);
+            CellBroadcastReceiverApp.clearNewMessageList();
+        }
+    }
+
+    /**
+     * Save the list of messages so the state can be restored later.
+     * @param outState Bundle in which to place the saved state.
+     */
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putParcelableArrayList(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, mMessageList);
+        Log.d(TAG, "onSaveInstanceState saved message list to bundle");
+    }
+
+    /** Returns the resource ID for either the full screen or dialog layout. */
+    protected int getLayoutResId() {
+        return R.layout.cell_broadcast_alert_fullscreen;
+    }
+
+    /** Update alert text when a new emergency alert arrives. */
+    private void updateAlertText(CellBroadcastMessage message) {
+        int titleId = CellBroadcastResources.getDialogTitleResource(message);
+        setTitle(titleId);
+        ((TextView) findViewById(R.id.alertTitle)).setText(titleId);
+        ((TextView) findViewById(R.id.message)).setText(message.getMessageBody());
     }
 
     /**
      * Start animating warning icon.
      */
     @Override
-    protected void onStart() {
-        super.onStart();
-        if (mIsEmergencyAlert) {
-            // start icon animation
-            mAnimationHandler.sendEmptyMessageDelayed(0, WARNING_ICON_ON_DURATION_MSEC);
+    protected void onResume() {
+        Log.d(TAG, "onResume called");
+        super.onResume();
+        CellBroadcastMessage message = getLatestMessage();
+        if (message != null && CellBroadcastConfigService.isEmergencyAlertMessage(message)) {
+            mAnimationHandler.startIconAnimation();
         }
     }
 
     /**
+     * Stop animating warning icon.
+     */
+    @Override
+    protected void onPause() {
+        Log.d(TAG, "onPause called");
+        mAnimationHandler.stopIconAnimation();
+        super.onPause();
+    }
+
+    /**
      * Stop animating warning icon and stop the {@link CellBroadcastAlertAudio}
      * service if necessary.
      */
@@ -179,7 +386,15 @@
         // Stop playing alert sound/vibration/speech (if started)
         stopService(new Intent(this, CellBroadcastAlertAudio.class));
 
-        final long deliveryTime = mMessage.getDeliveryTime();
+        // Remove the current alert message from the list.
+        CellBroadcastMessage lastMessage = removeLatestMessage();
+        if (lastMessage == null) {
+            Log.e(TAG, "dismiss() called with empty message list!");
+            return;
+        }
+
+        // Mark the alert as read.
+        final long deliveryTime = lastMessage.getDeliveryTime();
 
         // Mark broadcast as read on a background thread.
         new CellBroadcastContentProvider.AsyncCellBroadcastTask(getContentResolver())
@@ -191,19 +406,55 @@
                     }
                 });
 
-        if (mIsEmergencyAlert) {
-            // stop animating emergency alert icon
-            mStopAnimation = true;
-        } else {
-            // decrement unread non-emergency alert count
-            CellBroadcastReceiverApp.decrementUnreadAlertCount();
+        // Set the opt-out dialog flag if this is a CMAS alert (other than Presidential Alert).
+        if (lastMessage.isCmasMessage() && lastMessage.getCmasMessageClass() !=
+                SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT) {
+            mShowOptOutDialog = true;
         }
+
+        // If there are older emergency alerts to display, update the alert text and return.
+        CellBroadcastMessage nextMessage = getLatestMessage();
+        if (nextMessage != null) {
+            updateAlertText(nextMessage);
+            if (CellBroadcastConfigService.isEmergencyAlertMessage(nextMessage)) {
+                mAnimationHandler.startIconAnimation();
+            } else {
+                mAnimationHandler.stopIconAnimation();
+            }
+            return;
+        }
+
+        // Remove pending screen-off messages (animation messages are removed in onPause()).
+        mScreenOffHandler.stopScreenOnTimer();
+
+        // Show opt-in/opt-out dialog when the first CMAS alert is received.
+        if (mShowOptOutDialog) {
+            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+            if (prefs.getBoolean(CellBroadcastSettings.KEY_SHOW_CMAS_OPT_OUT_DIALOG, true)) {
+                // Clear the flag so the user will only see the opt-out dialog once.
+                prefs.edit().putBoolean(CellBroadcastSettings.KEY_SHOW_CMAS_OPT_OUT_DIALOG, false)
+                        .apply();
+
+                KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
+                if (km.inKeyguardRestrictedInputMode()) {
+                    Log.d(TAG, "Showing opt-out dialog in new activity (secure keyguard)");
+                    Intent intent = new Intent(this, CellBroadcastOptOutActivity.class);
+                    startActivity(intent);
+                } else {
+                    Log.d(TAG, "Showing opt-out dialog in current activity");
+                    CellBroadcastOptOutActivity.showOptOutDialog(this);
+                    return; // don't call finish() until user dismisses the dialog
+                }
+            }
+        }
+
         finish();
     }
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        if (!mMessage.isEtwsMessage()) {
+        CellBroadcastMessage message = getLatestMessage();
+        if (message != null && !message.isEtwsMessage()) {
             switch (event.getKeyCode()) {
                 // Volume keys and camera keys mute the alert sound/vibration (except ETWS).
                 case KeyEvent.KEYCODE_VOLUME_UP:
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
index 04fea0f..07e1bfb 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertService.java
@@ -26,7 +26,6 @@
 import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.os.PowerManager;
 import android.preference.PreferenceManager;
 import android.provider.Telephony;
 import android.telephony.CellBroadcastMessage;
@@ -35,6 +34,7 @@
 import android.telephony.SmsCbMessage;
 import android.util.Log;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 
 /**
@@ -46,25 +46,12 @@
 public class CellBroadcastAlertService extends Service {
     private static final String TAG = "CellBroadcastAlertService";
 
-    /** Identifier for notification ID extra. */
-    public static final String SMS_CB_NOTIFICATION_ID_EXTRA =
-            "com.android.cellbroadcastreceiver.SMS_CB_NOTIFICATION_ID";
-
-    /** Intent extra to indicate a previously unread alert. */
-    static final String NEW_ALERT_EXTRA = "com.android.cellbroadcastreceiver.NEW_ALERT";
-
     /** Intent action to display alert dialog/notification, after verifying the alert is new. */
     static final String SHOW_NEW_ALERT_ACTION = "cellbroadcastreceiver.SHOW_NEW_ALERT";
 
     /** Use the same notification ID for non-emergency alerts. */
     static final int NOTIFICATION_ID = 1;
 
-    /** CPU wake lock while handling emergency alert notification. */
-    private PowerManager.WakeLock mWakeLock;
-
-    /** Hold the wake lock for 5 seconds, which should be enough time to display the alert. */
-    private static final int WAKE_LOCK_TIMEOUT = 5000;
-
     /** Container for message ID and geographical scope, for duplicate message detection. */
     private static final class MessageIdAndScope {
         private final int mMessageId;
@@ -178,8 +165,7 @@
             return;
         }
 
-        if (cbm.isEmergencyAlertMessage() || CellBroadcastConfigService
-                .isOperatorDefinedEmergencyId(cbm.getServiceCategory())) {
+        if (CellBroadcastConfigService.isEmergencyAlertMessage(cbm)) {
             // start alert sound / vibration / TTS and display full-screen alert
             openEmergencyAlertNotification(cbm);
         } else {
@@ -233,25 +219,13 @@
         return true;    // other broadcast messages are always enabled
     }
 
-    private void acquireTimedWakelock(int timeout) {
-        if (mWakeLock == null) {
-            PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
-            // Note: acquiring a PARTIAL_WAKE_LOCK and setting window flag FLAG_TURN_SCREEN_ON in
-            // CellBroadcastAlertFullScreen is not sufficient to turn on the screen by itself.
-            // Use SCREEN_BRIGHT_WAKE_LOCK here as a workaround to ensure the screen turns on.
-            mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK
-                    | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
-        }
-        mWakeLock.acquire(timeout);
-    }
-
     /**
      * Display a full-screen alert message for emergency alerts.
      * @param message the alert to display
      */
     private void openEmergencyAlertNotification(CellBroadcastMessage message) {
         // Acquire a CPU wake lock until the alert dialog and audio start playing.
-        acquireTimedWakelock(WAKE_LOCK_TIMEOUT);
+        CellBroadcastAlertWakeLock.acquireScreenCpuWakeLock(this);
 
         // Close dialogs and window shade
         Intent closeDialogs = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
@@ -283,8 +257,6 @@
                     prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_VIBRATE, true));
         }
 
-        int channelTitleId = CellBroadcastResources.getDialogTitleResource(message);
-        CharSequence channelName = getText(channelTitleId);
         String messageBody = message.getMessageBody();
 
         if (prefs.getBoolean(CellBroadcastSettings.KEY_ENABLE_ALERT_SPEECH, true)) {
@@ -303,9 +275,6 @@
         }
         startService(audioIntent);
 
-        // Use lower 32 bits of emergency alert delivery time for notification ID
-        int notificationId = (int) message.getDeliveryTime();
-
         // Decide which activity to start based on the state of the keyguard.
         Class c = CellBroadcastAlertDialog.class;
         KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
@@ -314,23 +283,12 @@
             c = CellBroadcastAlertFullScreen.class;
         }
 
-        Intent notify = createDisplayMessageIntent(this, c, message, notificationId);
-        PendingIntent pi = PendingIntent.getActivity(this, notificationId, notify, 0);
+        ArrayList<CellBroadcastMessage> messageList = new ArrayList<CellBroadcastMessage>(1);
+        messageList.add(message);
 
-        Notification.Builder builder = new Notification.Builder(this)
-                .setSmallIcon(R.drawable.ic_notify_alert)
-                .setTicker(getText(CellBroadcastResources.getDialogTitleResource(message)))
-                .setWhen(System.currentTimeMillis())
-                .setContentIntent(pi)
-                .setFullScreenIntent(pi, true)
-                .setContentTitle(channelName)
-                .setContentText(messageBody)
-                .setDefaults(Notification.DEFAULT_LIGHTS);
-
-        NotificationManager notificationManager =
-            (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
-
-        notificationManager.notify(notificationId, builder.getNotification());
+        Intent alertDialogIntent = createDisplayMessageIntent(this, c, messageList);
+        alertDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        startActivity(alertDialogIntent);
     }
 
     /**
@@ -343,11 +301,17 @@
         CharSequence channelName = getText(channelTitleId);
         String messageBody = message.getMessageBody();
 
-        // Use the same ID to create a single notification for multiple non-emergency alerts.
-        int notificationId = NOTIFICATION_ID;
+        // Pass the list of unread non-emergency CellBroadcastMessages
+        ArrayList<CellBroadcastMessage> messageList = CellBroadcastReceiverApp
+                .addNewMessageToList(message);
 
-        PendingIntent pi = PendingIntent.getActivity(this, 0, createDisplayMessageIntent(
-                this, CellBroadcastListActivity.class, message, notificationId), 0);
+        // Create intent to show the new messages when user selects the notification.
+        Intent intent = createDisplayMessageIntent(this, CellBroadcastAlertDialog.class,
+                messageList);
+        intent.putExtra(CellBroadcastAlertFullScreen.FROM_NOTIFICATION_EXTRA, true);
+
+        PendingIntent pi = PendingIntent.getActivity(this, 0, intent,
+                PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
 
         // use default sound/vibration/lights for non-emergency broadcasts
         Notification.Builder builder = new Notification.Builder(this)
@@ -360,7 +324,7 @@
         builder.setDefaults(Notification.DEFAULT_ALL);
 
         // increment unread alert count (decremented when user dismisses alert dialog)
-        int unreadCount = CellBroadcastReceiverApp.incrementUnreadAlertCount();
+        int unreadCount = messageList.size();
         if (unreadCount > 1) {
             // use generic count of unread broadcasts if more than one unread
             builder.setContentTitle(getString(R.string.notification_multiple_title));
@@ -369,27 +333,17 @@
             builder.setContentTitle(channelName).setContentText(messageBody);
         }
 
-        Log.i(TAG, "addToNotificationBar notificationId: " + notificationId);
-
         NotificationManager notificationManager =
             (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
 
-        notificationManager.notify(notificationId, builder.getNotification());
+        notificationManager.notify(NOTIFICATION_ID, builder.build());
     }
 
     static Intent createDisplayMessageIntent(Context context, Class intentClass,
-            CellBroadcastMessage message, int notificationId) {
+            ArrayList<CellBroadcastMessage> messageList) {
         // Trigger the list activity to fire up a dialog that shows the received messages
         Intent intent = new Intent(context, intentClass);
-        intent.putExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, message);
-        intent.putExtra(SMS_CB_NOTIFICATION_ID_EXTRA, notificationId);
-        intent.putExtra(NEW_ALERT_EXTRA, true);
-
-        // This line is needed to make this intent compare differently than the other intents
-        // created here for other messages. Without this line, the PendingIntent always gets the
-        // intent of a previous message and notification.
-        intent.setType(Integer.toString(notificationId));
-
+        intent.putParcelableArrayListExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, messageList);
         return intent;
     }
 
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertWakeLock.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertWakeLock.java
new file mode 100644
index 0000000..a1360b8
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertWakeLock.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2012 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.cellbroadcastreceiver;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.util.Log;
+
+/**
+ * Hold a wakelock that can be acquired in the CellBroadcastAlertService and
+ * released in the CellBroadcastAlertFullScreen Activity.
+ */
+class CellBroadcastAlertWakeLock {
+    private static final String TAG = "CellBroadcastAlertWakeLock";
+
+    private static PowerManager.WakeLock sCpuWakeLock;
+
+    private CellBroadcastAlertWakeLock() {}
+
+    static void acquireScreenCpuWakeLock(Context context) {
+        if (sCpuWakeLock != null) {
+            return;
+        }
+        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        sCpuWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK
+                | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, TAG);
+        sCpuWakeLock.acquire();
+        Log.d(TAG, "acquired screen + CPU wake lock");
+    }
+
+    static void releaseCpuLock() {
+        if (sCpuWakeLock != null) {
+            sCpuWakeLock.release();
+            sCpuWakeLock = null;
+            Log.d(TAG, "released screen + CPU wake lock");
+        }
+    }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java b/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java
index ad77dc2..dd99dc5 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastConfigService.java
@@ -22,6 +22,7 @@
 import android.content.res.Resources;
 import android.os.SystemProperties;
 import android.preference.PreferenceManager;
+import android.telephony.CellBroadcastMessage;
 import android.telephony.SmsManager;
 import android.text.TextUtils;
 import android.util.Log;
@@ -80,13 +81,24 @@
         }
     }
 
-    static boolean isOperatorDefinedEmergencyId(int messageId) {
+    /**
+     * Returns true if this is a standard or operator-defined emergency alert message.
+     * This includes all ETWS and CMAS alerts, except for AMBER alerts.
+     * @param message the message to test
+     * @return true if the message is an emergency alert; false otherwise
+     */
+    static boolean isEmergencyAlertMessage(CellBroadcastMessage message) {
+        if (message.isEmergencyAlertMessage()) {
+            return true;
+        }
+
         // Check for system property defining the emergency channel ranges to enable
         String emergencyIdRange = SystemProperties.get("ro.cellbroadcast.emergencyids");
         if (TextUtils.isEmpty(emergencyIdRange)) {
             return false;
         }
         try {
+            int messageId = message.getServiceCategory();
             for (String channelRange : emergencyIdRange.split(",")) {
                 int dashIndex = channelRange.indexOf('-');
                 if (dashIndex != -1) {
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java b/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java
index f368725..7460f78 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastContentProvider.java
@@ -252,19 +252,15 @@
     /**
      * Internal method to delete a cell broadcast by row ID and notify observers.
      * @param rowId the row ID of the broadcast to delete
-     * @param decrementUnreadCount true to decrement the count of unread alerts
      * @return true if the database was updated, false otherwise
      */
-    boolean deleteBroadcast(long rowId, boolean decrementUnreadCount) {
+    boolean deleteBroadcast(long rowId) {
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
 
         int rowCount = db.delete(CellBroadcastDatabaseHelper.TABLE_NAME,
                 Telephony.CellBroadcasts._ID + "=?",
                 new String[]{Long.toString(rowId)});
         if (rowCount != 0) {
-            if (decrementUnreadCount) {
-                CellBroadcastReceiverApp.decrementUnreadAlertCount();
-            }
             return true;
         } else {
             Log.e(TAG, "failed to delete broadcast at row " + rowId);
@@ -281,7 +277,6 @@
 
         int rowCount = db.delete(CellBroadcastDatabaseHelper.TABLE_NAME, null, null);
         if (rowCount != 0) {
-            CellBroadcastReceiverApp.resetUnreadAlertCount();
             return true;
         } else {
             Log.e(TAG, "failed to delete all broadcasts");
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java b/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java
index 112cb92..777c24e 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastListActivity.java
@@ -44,6 +44,8 @@
 import android.widget.CursorAdapter;
 import android.widget.ListView;
 
+import java.util.ArrayList;
+
 /**
  * This activity provides a list view of received cell broadcasts. Most of the work is handled
  * in the inner CursorLoaderListFragment class.
@@ -159,7 +161,9 @@
         private void showDialogAndMarkRead(CellBroadcastMessage cbm) {
             // show emergency alerts with the warning icon, but don't play alert tone
             Intent i = new Intent(getActivity(), CellBroadcastAlertDialog.class);
-            i.putExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, cbm);
+            ArrayList<CellBroadcastMessage> messageList = new ArrayList<CellBroadcastMessage>(1);
+            messageList.add(cbm);
+            i.putParcelableArrayListExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, messageList);
             startActivity(i);
         }
 
@@ -190,11 +194,8 @@
             if (cursor != null && cursor.getPosition() >= 0) {
                 switch (item.getItemId()) {
                     case MENU_DELETE:
-                        // We need to decrement the unread alert count if deleting unread alert
-                        boolean isUnread = (cursor.getInt(cursor.getColumnIndexOrThrow(
-                                Telephony.CellBroadcasts.MESSAGE_READ)) == 0);
                         confirmDeleteThread(cursor.getLong(cursor.getColumnIndexOrThrow(
-                                Telephony.CellBroadcasts._ID)), isUnread);
+                                Telephony.CellBroadcasts._ID)));
                         break;
 
                     case MENU_VIEW_DETAILS:
@@ -212,7 +213,7 @@
         public boolean onOptionsItemSelected(MenuItem item) {
             switch(item.getItemId()) {
                 case MENU_DELETE_ALL:
-                    confirmDeleteThread(-1, false);
+                    confirmDeleteThread(-1);
                     break;
 
                 case MENU_PREFERENCES:
@@ -229,10 +230,9 @@
         /**
          * Start the process of putting up a dialog to confirm deleting a broadcast.
          * @param rowId the row ID of the broadcast to delete, or -1 to delete all broadcasts
-         * @param unread true if the alert was not already marked as read
          */
-        public void confirmDeleteThread(long rowId, boolean unread) {
-            DeleteThreadListener listener = new DeleteThreadListener(rowId, unread);
+        public void confirmDeleteThread(long rowId) {
+            DeleteThreadListener listener = new DeleteThreadListener(rowId);
             confirmDeleteThreadDialog(listener, (rowId == -1), getActivity());
         }
 
@@ -258,11 +258,9 @@
 
         public class DeleteThreadListener implements OnClickListener {
             private final long mRowId;
-            private final boolean mIsUnread;
 
-            public DeleteThreadListener(long rowId, boolean unread) {
+            public DeleteThreadListener(long rowId) {
                 mRowId = rowId;
-                mIsUnread = unread;
             }
 
             @Override
@@ -274,7 +272,7 @@
                             @Override
                             public boolean execute(CellBroadcastContentProvider provider) {
                                 if (mRowId != -1) {
-                                    return provider.deleteBroadcast(mRowId, mIsUnread);
+                                    return provider.deleteBroadcast(mRowId);
                                 } else {
                                     return provider.deleteAllBroadcasts();
                                 }
@@ -285,29 +283,4 @@
             }
         }
     }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        if (intent == null) {
-            return;
-        }
-
-        Bundle extras = intent.getExtras();
-        if (extras == null) {
-            return;
-        }
-
-        CellBroadcastMessage cbm = extras.getParcelable(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA);
-        int notificationId = extras.getInt(CellBroadcastAlertService.SMS_CB_NOTIFICATION_ID_EXTRA);
-
-        // Dismiss the notification that brought us here.
-        NotificationManager notificationManager =
-            (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
-        notificationManager.cancel(notificationId);
-
-        // launch the dialog activity to show the alert
-        Intent i = new Intent(this, CellBroadcastAlertDialog.class);
-        i.putExtra(CellBroadcastMessage.SMS_CB_MESSAGE_EXTRA, cbm);
-        startActivity(i);
-    }
 }
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastOptOutActivity.java b/src/com/android/cellbroadcastreceiver/CellBroadcastOptOutActivity.java
new file mode 100644
index 0000000..76ed537
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastOptOutActivity.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2012 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.cellbroadcastreceiver;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Container activity for CMAS opt-in/opt-out alert dialog.
+ */
+public class CellBroadcastOptOutActivity extends Activity {
+    private static final String TAG = "CellBroadcastOptOutActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.d(TAG, "created activity");
+        showOptOutDialog(this);
+    }
+
+    /**
+     * Show the opt-out dialog. Uses the CellBroadcastAlertDialog activity unless the device is
+     * in restricted keyguard mode, in which case we create a new CellBroadcastOptOutActivity
+     * so that the dialog appears underneath the lock screen. The user must unlock the device
+     * to configure the settings, so we don't want to show the opt-in dialog before then.
+     */
+    static void showOptOutDialog(final Activity activity) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+        builder.setMessage(R.string.cmas_opt_out_dialog_text)
+                .setPositiveButton(R.string.cmas_opt_out_button_yes,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                Log.d(TAG, "User clicked Yes");
+                                activity.finish();
+                            }
+                        })
+                .setNegativeButton(R.string.cmas_opt_out_button_no,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                Log.d(TAG, "User clicked No");
+                                Intent intent = new Intent(activity, CellBroadcastSettings.class);
+                                activity.startActivity(intent);
+                                activity.finish();
+                            }
+                        })
+                .create().show();
+    }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java
index eb21e17..65e8c72 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastReceiverApp.java
@@ -17,9 +17,11 @@
 package com.android.cellbroadcastreceiver;
 
 import android.app.Application;
+import android.telephony.CellBroadcastMessage;
 import android.util.Log;
 import android.preference.PreferenceManager;
 
+import java.util.ArrayList;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -36,29 +38,18 @@
         PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
     }
 
-    /** Number of unread non-emergency alerts since the device was booted. */
-    private static AtomicInteger sUnreadAlertCount = new AtomicInteger();
+    /** List of unread non-emergency alerts to show when user selects the notification. */
+    private static final ArrayList<CellBroadcastMessage> sNewMessageList =
+            new ArrayList<CellBroadcastMessage>(4);
 
-    /**
-     * Increments the number of unread non-emergency alerts, returning the new value.
-     * @return the updated number of unread non-emergency alerts, after incrementing
-     */
-    static int incrementUnreadAlertCount() {
-        return sUnreadAlertCount.incrementAndGet();
+    /** Adds a new unread non-emergency message and returns the current list. */
+    static ArrayList<CellBroadcastMessage> addNewMessageToList(CellBroadcastMessage message) {
+        sNewMessageList.add(message);
+        return sNewMessageList;
     }
 
-    /**
-     * Decrements the number of unread non-emergency alerts after the user reads it.
-     */
-    static void decrementUnreadAlertCount() {
-        if (sUnreadAlertCount.decrementAndGet() < 0) {
-            Log.e(TAG, "mUnreadAlertCount < 0, resetting to 0");
-            sUnreadAlertCount.set(0);
-        }
-    }
-
-    /** Resets the unread alert count to zero after user deletes all alerts. */
-    static void resetUnreadAlertCount() {
-        sUnreadAlertCount.set(0);
+    /** Clears the list of unread non-emergency messages. */
+    static void clearNewMessageList() {
+        sNewMessageList.clear();
     }
 }
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastResources.java b/src/com/android/cellbroadcastreceiver/CellBroadcastResources.java
index fedd153..76d4b42 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastResources.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastResources.java
@@ -289,7 +289,7 @@
             }
         }
 
-        if (cbm.isPublicAlertMessage()) {
+        if (CellBroadcastConfigService.isEmergencyAlertMessage(cbm)) {
             return R.string.pws_other_message_identifiers;
         } else {
             return R.string.cb_other_message_identifiers;
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java b/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
index 7e7915d..a7c7482 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
@@ -79,6 +79,9 @@
     // Enabled by default for phones sold in Brazil, otherwise this setting may be hidden.
     public static final String KEY_ENABLE_CHANNEL_50_ALERTS = "enable_channel_50_alerts";
 
+    // Preference key for initial opt-in/opt-out dialog.
+    public static final String KEY_SHOW_CMAS_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
diff --git a/tests/res/layout/test_buttons.xml b/tests/res/layout/test_buttons.xml
index 2dd3849..96103cb 100644
--- a/tests/res/layout/test_buttons.xml
+++ b/tests/res/layout/test_buttons.xml
@@ -25,6 +25,23 @@
     android:layout_height="match_parent"
     android:orientation="vertical">
 
+    <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:orientation="horizontal">
+
+      <TextView android:id="@+id/message_id_label"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:text="@string/message_id_label" />
+
+      <EditText android:id="@+id/message_id"
+          android:layout_width="80dp"
+          android:layout_height="wrap_content"
+          android:inputType="number" />
+
+    </LinearLayout>
+
     <CheckBox android:id="@+id/button_delay_broadcast"
         android:text="@string/button_delay_broadcast"
         android:layout_marginLeft="20dp"
diff --git a/tests/res/values/strings.xml b/tests/res/values/strings.xml
index 5968e1c..b1b1031 100644
--- a/tests/res/values/strings.xml
+++ b/tests/res/values/strings.xml
@@ -39,4 +39,5 @@
     <string name="button_gsm_ucs2_with_language_type">Send GSM UCS-2 With Language</string>
     <string name="button_gsm_ucs2_with_language_umts_type">Send UMTS UCS-2 With Language</string>
     <string name="button_delay_broadcast">Delay 5 seconds before sending</string>
+    <string name="message_id_label">Message ID:</string>
 </resources>
diff --git a/tests/src/com/android/cellbroadcastreceiver/tests/SendCdmaCmasMessages.java b/tests/src/com/android/cellbroadcastreceiver/tests/SendCdmaCmasMessages.java
index 17f76cb..71ca812 100644
--- a/tests/src/com/android/cellbroadcastreceiver/tests/SendCdmaCmasMessages.java
+++ b/tests/src/com/android/cellbroadcastreceiver/tests/SendCdmaCmasMessages.java
@@ -62,9 +62,9 @@
 
     private static final String IS91_TEXT = "IS91 SHORT MSG";   // max length 14 chars
 
-    public static void testSendCmasPresAlert(Activity activity) {
+    public static void testSendCmasPresAlert(Activity activity, int messageId) {
         SmsCbMessage cbMessage = createCmasSmsMessage(
-                SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, 12345, "en",
+                SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, messageId, "en",
                 PRES_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_GEO,
                 SmsCbCmasInfo.CMAS_RESPONSE_TYPE_PREPARE, SmsCbCmasInfo.CMAS_SEVERITY_EXTREME,
                 SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY);
@@ -74,9 +74,9 @@
         activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
     }
 
-    public static void testSendCmasExtremeAlert(Activity activity) {
+    public static void testSendCmasExtremeAlert(Activity activity, int messageId) {
         SmsCbMessage cbMessage = createCmasSmsMessage(
-                SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, 23456, "en",
+                SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, messageId, "en",
                 EXTREME_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_MET,
                 SmsCbCmasInfo.CMAS_RESPONSE_TYPE_PREPARE, SmsCbCmasInfo.CMAS_SEVERITY_EXTREME,
                 SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED);
@@ -86,9 +86,9 @@
         activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
     }
 
-    public static void testSendCmasSevereAlert(Activity activity) {
+    public static void testSendCmasSevereAlert(Activity activity, int messageId) {
         SmsCbMessage cbMessage = createCmasSmsMessage(
-                SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, 34567, "en",
+                SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, messageId, "en",
                 SEVERE_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_HEALTH,
                 SmsCbCmasInfo.CMAS_RESPONSE_TYPE_AVOID, SmsCbCmasInfo.CMAS_SEVERITY_SEVERE,
                 SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE, SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY);
@@ -98,9 +98,9 @@
         activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
     }
 
-    public static void testSendCmasAmberAlert(Activity activity) {
+    public static void testSendCmasAmberAlert(Activity activity, int messageId) {
         SmsCbMessage cbMessage = createCmasSmsMessage(
-                SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, 45678, "en",
+                SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY, messageId, "en",
                 AMBER_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN,
                 SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN,
                 SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN, SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN);
@@ -110,9 +110,9 @@
         activity.sendOrderedBroadcast(intent, "android.permission.RECEIVE_SMS");
     }
 
-    public static void testSendCmasMonthlyTest(Activity activity) {
+    public static void testSendCmasMonthlyTest(Activity activity, int messageId) {
         SmsCbMessage cbMessage = createCmasSmsMessage(
-                SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE, 56789, "en",
+                SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE, messageId, "en",
                 MONTHLY_TEST_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN,
                 SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN,
                 SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN, SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN);
diff --git a/tests/src/com/android/cellbroadcastreceiver/tests/SendTestBroadcastActivity.java b/tests/src/com/android/cellbroadcastreceiver/tests/SendTestBroadcastActivity.java
index 0e73101..f47ffd2 100644
--- a/tests/src/com/android/cellbroadcastreceiver/tests/SendTestBroadcastActivity.java
+++ b/tests/src/com/android/cellbroadcastreceiver/tests/SendTestBroadcastActivity.java
@@ -20,16 +20,20 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.CheckBox;
+import android.widget.EditText;
+
+import java.util.Random;
 
 /**
  * Activity to send test cell broadcast messages from GUI.
  */
 public class SendTestBroadcastActivity extends Activity {
-    private static String TAG = "SendTestBroadcastActivity";
+    private static final String TAG = "SendTestBroadcastActivity";
 
     /** Whether to delay before sending test message. */
     private boolean mDelayBeforeSending;
@@ -37,19 +41,35 @@
     /** Delay time before sending test message (when box is checked). */
     private static final int DELAY_BEFORE_SENDING_MSEC = 5000;
 
-    /** Callback for sending test message after delay */
-    private OnClickListener mPendingButtonClick;
-
-    private Handler mDelayHandler = new Handler() {
+    private final Handler mDelayHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             // call the onClick() method again, passing null View.
             // The callback will ignore mDelayBeforeSending when the View is null.
-            mPendingButtonClick.onClick(null);
+            OnClickListener pendingButtonClick = (OnClickListener) msg.obj;
+            pendingButtonClick.onClick(null);
         }
     };
 
-
+    /**
+     * Increment the message ID field and return the previous value.
+     * @return the current value of the message ID text field
+     */
+    private int getMessageId() {
+        EditText messageIdField = (EditText) findViewById(R.id.message_id);
+        int messageId = 0;
+        try {
+            messageId = Integer.parseInt(messageIdField.getText().toString());
+        } catch (NumberFormatException ignored) {
+            Log.e(TAG, "Invalid message ID");
+        }
+        int newMessageId = (messageId + 1) % 65536;
+        if (newMessageId == 0) {
+            newMessageId = 1;
+        }
+        messageIdField.setText(String.valueOf(newMessageId));
+        return messageId;
+    }
 
     /**
      * Initialization of the Activity after it is first created.  Must at least
@@ -61,14 +81,18 @@
         super.onCreate(savedInstanceState);
 
         setContentView(R.layout.test_buttons);
+
+        /* Set message ID to a random value from 1-65535. */
+        EditText messageIdField = (EditText) findViewById(R.id.message_id);
+        messageIdField.setText(String.valueOf(new Random().nextInt(65535) + 1));
                 
         /* Send an ETWS normal broadcast message to app. */
         Button etwsNormalTypeButton = (Button) findViewById(R.id.button_etws_normal_type);
         etwsNormalTypeButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendEtwsMessageNormal(SendTestBroadcastActivity.this);
                 }
@@ -80,8 +104,8 @@
         etwsCancelTypeButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendEtwsMessageCancel(SendTestBroadcastActivity.this);
                 }
@@ -93,8 +117,8 @@
         etwsTestTypeButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendEtwsMessageTest(SendTestBroadcastActivity.this);
                 }
@@ -106,10 +130,11 @@
         cmasPresAlertButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
-                    SendCdmaCmasMessages.testSendCmasPresAlert(SendTestBroadcastActivity.this);
+                    SendCdmaCmasMessages.testSendCmasPresAlert(SendTestBroadcastActivity.this,
+                            getMessageId());
                 }
             }
         });
@@ -119,10 +144,11 @@
         cmasExtremeAlertButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
-                    SendCdmaCmasMessages.testSendCmasExtremeAlert(SendTestBroadcastActivity.this);
+                    SendCdmaCmasMessages.testSendCmasExtremeAlert(SendTestBroadcastActivity.this,
+                            getMessageId());
                 }
             }
         });
@@ -132,10 +158,11 @@
         cmasSevereAlertButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
-                    SendCdmaCmasMessages.testSendCmasSevereAlert(SendTestBroadcastActivity.this);
+                    SendCdmaCmasMessages.testSendCmasSevereAlert(SendTestBroadcastActivity.this,
+                            getMessageId());
                 }
             }
         });
@@ -145,10 +172,11 @@
         cmasAmberAlertButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
-                    SendCdmaCmasMessages.testSendCmasAmberAlert(SendTestBroadcastActivity.this);
+                    SendCdmaCmasMessages.testSendCmasAmberAlert(SendTestBroadcastActivity.this,
+                            getMessageId());
                 }
             }
         });
@@ -158,10 +186,11 @@
         cmasMonthlyTestButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
-                    SendCdmaCmasMessages.testSendCmasMonthlyTest(SendTestBroadcastActivity.this);
+                    SendCdmaCmasMessages.testSendCmasMonthlyTest(SendTestBroadcastActivity.this,
+                            getMessageId());
                 }
             }
         });
@@ -171,8 +200,8 @@
         gsm7bitTypeButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendMessage7bit(SendTestBroadcastActivity.this);
                 }
@@ -184,8 +213,8 @@
         gsm7bitUmtsTypeButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendMessage7bitUmts(SendTestBroadcastActivity.this);
                 }
@@ -197,8 +226,8 @@
         gsm7bitNoPaddingButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendMessage7bitNoPadding(SendTestBroadcastActivity.this);
                 }
@@ -211,8 +240,8 @@
         gsm7bitNoPaddingUmtsTypeButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendMessage7bitNoPaddingUmts(SendTestBroadcastActivity.this);
                 }
@@ -225,8 +254,8 @@
         gsm7bitMultipageButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendMessage7bitMultipageGsm(SendTestBroadcastActivity.this);
                 }
@@ -239,8 +268,8 @@
         gsm7bitMultipageUmtsButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendMessage7bitMultipageUmts(SendTestBroadcastActivity.this);
                 }
@@ -253,8 +282,8 @@
         gsm7bitWithLanguageButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendMessage7bitWithLanguage(SendTestBroadcastActivity.this);
                 }
@@ -267,8 +296,8 @@
         gsm7bitWithLanguageInBodyButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendMessage7bitWithLanguageInBody(
                             SendTestBroadcastActivity.this);
@@ -282,8 +311,8 @@
         gsm7bitWithLanguageUmtsButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendMessage7bitWithLanguageInBodyUmts(SendTestBroadcastActivity.this);
                 }
@@ -295,8 +324,8 @@
         gsmUcs2TypeButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendMessageUcs2(SendTestBroadcastActivity.this);
                 }
@@ -308,8 +337,8 @@
         gsmUcs2UmtsTypeButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendMessageUcs2Umts(SendTestBroadcastActivity.this);
                 }
@@ -322,8 +351,8 @@
         gsmUcs2MultipageUmtsTypeButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendMessageUcs2MultipageUmts(SendTestBroadcastActivity.this);
                 }
@@ -336,8 +365,8 @@
         gsmUcs2WithLanguageTypeButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendMessageUcs2WithLanguageInBody(SendTestBroadcastActivity.this);
                 }
@@ -350,8 +379,8 @@
         gsmUcs2WithLanguageUmtsTypeButton.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
                 if (mDelayBeforeSending && v != null) {
-                    mPendingButtonClick = this;
-                    mDelayHandler.sendEmptyMessageDelayed(0, DELAY_BEFORE_SENDING_MSEC);
+                    Message msg = mDelayHandler.obtainMessage(0, this);
+                    mDelayHandler.sendMessageDelayed(msg, DELAY_BEFORE_SENDING_MSEC);
                 } else {
                     SendTestMessages.testSendMessageUcs2WithLanguageUmts(SendTestBroadcastActivity.this);
                 }