summaryrefslogtreecommitdiffstats
path: root/src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java
diff options
context:
space:
mode:
authorJake Hamby <jhamby@google.com>2012-10-08 19:41:30 -0700
committerJake Hamby <jhamby@google.com>2012-10-12 14:47:59 -0700
commit57273ebfa13f96bf5aba9902b70e2b179fec9e4c (patch)
tree1355d9061b21f2f81a2c87a6eea51801be497c8d /src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java
parentd2869a10f34780efe8eecfe2719faaa91494a1b3 (diff)
downloadandroid_packages_apps_CellBroadcastReceiver-57273ebfa13f96bf5aba9902b70e2b179fec9e4c.tar.gz
android_packages_apps_CellBroadcastReceiver-57273ebfa13f96bf5aba9902b70e2b179fec9e4c.tar.bz2
android_packages_apps_CellBroadcastReceiver-57273ebfa13f96bf5aba9902b70e2b179fec9e4c.zip
Multiple fixes to CMAS app.
* Allow screen to turn off after 30 seconds for emergency alerts. * When multiple alerts are received, show them in reverse order. * After showing the first CMAS alert received on device (other than Presidential Alert), show opt-out dialog to allow user to opt-in or out of the various levels of CMAS alert. * Start/stop animating warning icon in activity pause/resume. * When multiple non-emergency alerts are received, show them all (most recent displayed first) when user selects the notification. * For emergency alerts, start the dialog activity directly instead of creating a PendingIntent and full screen notification. * Correctly save/restore the list of alerts in the alert dialog in onSaveInstanceState() when recreated after screen rotation. * Fix test app to increment the message ID and to send the correct alerts when multiple alerts are selected with 5 second delay. Bug: 6993660 Bug: 7041847 Bug: 7045506 Change-Id: Ic3ec08f0ebd693244891a4bf3a29479b832c2a3e
Diffstat (limited to 'src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java')
-rw-r--r--src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java403
1 files changed, 327 insertions, 76 deletions
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java
index 99404265..cf6d7e5a 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.Button;
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";
+
+ /** Intent extra for non-emergency alerts sent when user selects the notification. */
+ static final String FROM_NOTIFICATION_EXTRA = "from_notification";
- /** Whether to show the flashing warning icon. */
- private boolean mIsEmergencyAlert;
+ /** List of cell broadcast messages to display (oldest to newest). */
+ ArrayList<CellBroadcastMessage> mMessageList;
- /** The cell broadcast message to display. */
- CellBroadcastMessage mMessage;
+ /** 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 @@ public class CellBroadcastAlertFullScreen extends Activity {
/** 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;
+
+ /** Animation handler for the flashing warning icon (emergency alerts only). */
+ private final AnimationHandler mAnimationHandler = new AnimationHandler();
+
+ /** Handler to add and remove screen on flags for emergency alerts. */
+ private final ScreenOffHandler mScreenOffHandler = new ScreenOffHandler();
+
+ /**
+ * 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();
+
+ /** Warning icon state: visible == true, hidden == false. */
+ private boolean mWarningIconVisible;
- /** Stop animating icon after {@link #onStop()} is called. */
- private boolean mStopAnimation;
+ /** The warning icon Drawable. */
+ private Drawable mWarningIcon;
- /** The warning icon Drawable. */
- private Drawable mWarningIcon;
+ /** The View containing the warning icon. */
+ private ImageView mWarningIconView;
- /** 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
+ }
+ 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);
+ }
- /** 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);
+ 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;
}
- } else {
- mWarningIconView.setImageAlpha(0);
- if (!mStopAnimation) {
- mAnimationHandler.sendEmptyMessageDelayed(0, WARNING_ICON_OFF_DURATION_MSEC);
+ }
+ 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;
}
}
- mIconAnimationState = !mIconAnimationState;
- mWarningIconView.invalidateDrawable(mWarningIcon);
+ 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 @@ public class CellBroadcastAlertFullScreen extends Activity {
| 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);
+ // Initialize the view.
+ LayoutInflater inflater = LayoutInflater.from(this);
+ setContentView(inflater.inflate(getLayoutResId(), null));
+
+ findViewById(R.id.dismissButton).setOnClickListener(
+ new Button.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ }
+ });
+
+ // 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 we were started from a notification, dismiss it.
+ clearNotification(intent);
}
- // Save message for passing from dialog to fullscreen activity, and for marking read.
- mMessage = getIntent().getParcelableExtra(
+ 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");
+ }
+ }
- updateLayout(mMessage);
+ /** 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;
}
- private void updateLayout(CellBroadcastMessage message) {
- LayoutInflater inflater = LayoutInflater.from(this);
-
- setContentView(inflater.inflate(getLayoutResId(), null));
-
- /* Initialize dialog text from alert message. */
+ /** 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());
-
- /* dismiss button: close notification */
- findViewById(R.id.dismissButton).setOnClickListener(
- new Button.OnClickListener() {
- public void onClick(View v) {
- dismiss();
- }
- });
-
- mIsEmergencyAlert = message.isPublicAlertMessage() || CellBroadcastConfigService
- .isOperatorDefinedEmergencyId(message.getServiceCategory());
-
- 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());
- }
}
/**
* 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 @@ public class CellBroadcastAlertFullScreen extends Activity {
// 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 @@ public class CellBroadcastAlertFullScreen extends Activity {
}
});
- 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: