Merge "Add CMAS reminder alert feature." into jb-mr2-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 92fda98..921fe4a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -43,6 +43,9 @@
         <service android:name="CellBroadcastConfigService"
                  android:exported="false" />
 
+        <service android:name="CellBroadcastAlertReminder"
+                 android:exported="false" />
+
         <provider android:name="CellBroadcastContentProvider"
                   android:authorities="cellbroadcasts"
                   android:readPermission="android.permission.READ_CELL_BROADCASTS" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0a9b387..21ed190 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -93,6 +93,10 @@
     <string name="alert_sound_duration_title">Alert sound duration</string>
     <!-- Do not translate. Empty summary for alert duration (set by CellBroadcastSettings). -->
     <string name="alert_sound_duration_summary"></string>
+    <!-- Preference title for alert reminder interval list. [CHAR LIMIT=30] -->
+    <string name="alert_reminder_interval_title">Alert reminder</string>
+    <!-- Do not translate. Empty summary for alert reminder (set by CellBroadcastSettings). -->
+    <string name="alert_reminder_interval_summary"></string>
     <!-- Preference title for enable text-to-speech checkbox. [CHAR LIMIT=30] -->
     <string name="enable_alert_speech_title">Speak alert message</string>
     <!-- Preference summary for enable text-to-speech checkbox. [CHAR LIMIT=100] -->
@@ -243,4 +247,21 @@
       <item>8</item>
       <item>10</item>
     </string-array>
+
+    <!-- Entries in the ListPreference for alert reminder intervals. [CHAR LIMIT=30] -->
+    <string-array name="alert_reminder_interval_entries">
+      <item>Once</item>
+      <item>Every 2 minutes</item>
+      <item>Every 15 minutes</item>
+      <item>Off</item>
+    </string-array>
+
+    <!-- Do not translate. Values that are retrieved from the ListPreference.
+         These must match the alert_reminder_interval_entries list above. -->
+    <string-array name="alert_reminder_interval_values">
+      <item>1</item>
+      <item>2</item>
+      <item>15</item>
+      <item>0</item>
+    </string-array>
 </resources>
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 340830a..8d3b1fa 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -52,6 +52,13 @@
                         android:defaultValue="4"
                         android:dialogTitle="@string/alert_sound_duration_title" />
 
+        <ListPreference android:key="alert_reminder_interval"
+                        android:title="@string/alert_reminder_interval_title"
+                        android:entries="@array/alert_reminder_interval_entries"
+                        android:entryValues="@array/alert_reminder_interval_values"
+                        android:defaultValue="0"
+                        android:dialogTitle="@string/alert_reminder_interval_title" />
+
         <CheckBoxPreference android:defaultValue="true"
                             android:key="enable_alert_vibrate"
                             android:summary="@string/enable_alert_vibrate_summary"
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
index bc44ed1..52ed4e1 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
@@ -16,6 +16,7 @@
 
 package com.android.cellbroadcastreceiver;
 
+import android.app.PendingIntent;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
@@ -24,6 +25,9 @@
 import android.media.AudioManager;
 import android.media.MediaPlayer;
 import android.media.MediaPlayer.OnErrorListener;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
@@ -97,6 +101,8 @@
     private TelephonyManager mTelephonyManager;
     private int mInitialCallState;
 
+    private PendingIntent mPlayReminderIntent;
+
     // Internal messages
     private static final int ALERT_SOUND_FINISHED = 1000;
     private static final int ALERT_PAUSE_FINISHED = 1001;
@@ -125,14 +131,14 @@
                         mTts.speak(mMessageBody, TextToSpeech.QUEUE_FLUSH, null);
                         mState = STATE_SPEAKING;
                     } else {
-                        Log.w(TAG, "TTS engine not ready or language not supported");
+                        loge("TTS engine not ready or language not supported");
                         stopSelf();
                         mState = STATE_IDLE;
                     }
                     break;
 
                 default:
-                    Log.e(TAG, "Handler received unknown message, what=" + msg.what);
+                    loge("Handler received unknown message, what=" + msg.what);
             }
         }
     };
@@ -162,7 +168,7 @@
         } else {
             mTtsEngineReady = false;
             mTts = null;
-            Log.e(TAG, "onInit() TTS engine error: " + status);
+            loge("onInit() TTS engine error: " + status);
         }
     }
 
@@ -216,7 +222,7 @@
                 mTts.shutdown();
             } catch (IllegalStateException e) {
                 // catch "Unable to retrieve AudioTrack pointer for stop()" exception
-                Log.e(TAG, "exception trying to shutdown text-to-speech");
+                loge("exception trying to shutdown text-to-speech");
             }
         }
         // release CPU wake lock acquired by CellBroadcastAlertService
@@ -245,12 +251,13 @@
         mMessageLanguage = intent.getStringExtra(ALERT_AUDIO_MESSAGE_LANGUAGE);
 
         mEnableVibrate = intent.getBooleanExtra(ALERT_AUDIO_VIBRATE_EXTRA, true);
-        boolean forceVibrate = intent.getBooleanExtra(ALERT_AUDIO_ETWS_VIBRATE_EXTRA, false);
+        if (intent.getBooleanExtra(ALERT_AUDIO_ETWS_VIBRATE_EXTRA, false)) {
+            mEnableVibrate = true;  // force enable vibration for ETWS alerts
+        }
 
         switch (mAudioManager.getRingerMode()) {
             case AudioManager.RINGER_MODE_SILENT:
                 if (DBG) log("Ringer mode: silent");
-                mEnableVibrate = forceVibrate;
                 mEnableAudio = false;
                 break;
 
@@ -311,7 +318,7 @@
             mMediaPlayer = new MediaPlayer();
             mMediaPlayer.setOnErrorListener(new OnErrorListener() {
                 public boolean onError(MediaPlayer mp, int what, int extra) {
-                    Log.e(TAG, "Error occurred while playing audio.");
+                    loge("Error occurred while playing audio.");
                     mp.stop();
                     mp.release();
                     mMediaPlayer = null;
@@ -324,7 +331,7 @@
                 // sound at a low volume to not disrupt the call.
                 if (mTelephonyManager.getCallState()
                         != TelephonyManager.CALL_STATE_IDLE) {
-                    Log.v(TAG, "in call: reducing volume");
+                    log("in call: reducing volume");
                     mMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME);
                 }
 
@@ -335,7 +342,7 @@
                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                 startAlarm(mMediaPlayer);
             } catch (Exception ex) {
-                Log.e(TAG, "Failed to play alert sound", ex);
+                loge("Failed to play alert sound: " + ex);
             }
         }
 
@@ -363,12 +370,33 @@
         }
     }
 
+    private void playAlertReminderSound() {
+        Uri notificationUri = RingtoneManager.getDefaultUri(
+                RingtoneManager.TYPE_NOTIFICATION | RingtoneManager.TYPE_ALARM);
+        if (notificationUri == null) {
+            loge("Can't get URI for alert reminder sound");
+            return;
+        }
+        Ringtone r = RingtoneManager.getRingtone(this, notificationUri);
+        if (r != null) {
+            log("playing alert reminder sound");
+            r.play();
+        } else {
+            loge("can't get Ringtone for alert reminder sound");
+        }
+    }
+
     /**
      * Stops alert audio and speech.
      */
     public void stop() {
         if (DBG) log("stop()");
 
+        if (mPlayReminderIntent != null) {
+            mPlayReminderIntent.cancel();
+            mPlayReminderIntent = null;
+        }
+
         mHandler.removeMessages(ALERT_SOUND_FINISHED);
         mHandler.removeMessages(ALERT_PAUSE_FINISHED);
 
@@ -380,7 +408,7 @@
                     mMediaPlayer.release();
                 } catch (IllegalStateException e) {
                     // catch "Unable to retrieve AudioTrack pointer for stop()" exception
-                    Log.e(TAG, "exception trying to stop media player");
+                    loge("exception trying to stop media player");
                 }
                 mMediaPlayer = null;
             }
@@ -392,7 +420,7 @@
                 mTts.stop();
             } catch (IllegalStateException e) {
                 // catch "Unable to retrieve AudioTrack pointer for stop()" exception
-                Log.e(TAG, "exception trying to stop text-to-speech");
+                loge("exception trying to stop text-to-speech");
             }
         }
         mAudioManager.abandonAudioFocus(null);
@@ -402,4 +430,8 @@
     private static void log(String msg) {
         Log.d(TAG, msg);
     }
+
+    private static void loge(String msg) {
+        Log.e(TAG, msg);
+    }
 }
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java
index cf6d7e5..fdecc10 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertFullScreen.java
@@ -353,6 +353,9 @@
         setTitle(titleId);
         ((TextView) findViewById(R.id.alertTitle)).setText(titleId);
         ((TextView) findViewById(R.id.message)).setText(message.getMessageBody());
+
+        // Set alert reminder depending on user preference
+        CellBroadcastAlertReminder.queueAlertReminder(this, true);
     }
 
     /**
@@ -386,6 +389,9 @@
         // Stop playing alert sound/vibration/speech (if started)
         stopService(new Intent(this, CellBroadcastAlertAudio.class));
 
+        // Cancel any pending alert reminder
+        CellBroadcastAlertReminder.cancelAlertReminder();
+
         // Remove the current alert message from the list.
         CellBroadcastMessage lastMessage = removeLatestMessage();
         if (lastMessage == null) {
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertReminder.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertReminder.java
new file mode 100644
index 0000000..e4cae6c
--- /dev/null
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertReminder.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2013 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.AlarmManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import static com.android.cellbroadcastreceiver.CellBroadcastReceiver.DBG;
+
+/**
+ * Manages alert reminder notification.
+ */
+public class CellBroadcastAlertReminder extends Service {
+    private static final String TAG = "CellBroadcastAlertReminder";
+
+    /** Action to wake up and play alert reminder sound. */
+    static final String ACTION_PLAY_ALERT_REMINDER = "ACTION_PLAY_ALERT_REMINDER";
+
+    /**
+     * Pending intent for alert reminder. This is static so that we don't have to start the
+     * service in order to cancel any pending reminders when user dismisses the alert dialog.
+     */
+    private static PendingIntent sPlayReminderIntent;
+
+    /**
+     * Alert reminder for current ringtone being played.
+     */
+    private static Ringtone sPlayReminderRingtone;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        // No intent or unrecognized action; tell the system not to restart us.
+        if (intent == null || !ACTION_PLAY_ALERT_REMINDER.equals(intent.getAction())) {
+            stopSelf();
+            return START_NOT_STICKY;
+        }
+
+        log("playing alert reminder");
+        playAlertReminderSound();
+
+        if (queueAlertReminder(this, false)) {
+            return START_STICKY;
+        } else {
+            log("no reminders queued");
+            stopSelf();
+            return START_NOT_STICKY;
+        }
+    }
+
+    /**
+     * Use the RingtoneManager to play the alert reminder sound.
+     */
+    private void playAlertReminderSound() {
+        Uri notificationUri = RingtoneManager.getDefaultUri(
+                RingtoneManager.TYPE_NOTIFICATION | RingtoneManager.TYPE_ALARM);
+        if (notificationUri == null) {
+            loge("Can't get URI for alert reminder sound");
+            return;
+        }
+        Ringtone r = RingtoneManager.getRingtone(this, notificationUri);
+        if (r != null) {
+            log("playing alert reminder sound");
+            r.play();
+        } else {
+            loge("can't get Ringtone for alert reminder sound");
+        }
+    }
+
+    /**
+     * Helper method to start the alert reminder service to queue the alert reminder.
+     * @return true if a pending reminder was set; false if there are no more reminders
+     */
+    static boolean queueAlertReminder(Context context, boolean firstTime) {
+        // Stop any alert reminder sound and cancel any previously queued reminders.
+        cancelAlertReminder();
+
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        String prefStr = prefs.getString(CellBroadcastSettings.KEY_ALERT_REMINDER_INTERVAL, null);
+
+        if (prefStr == null) {
+            if (DBG) log("no preference value for alert reminder");
+            return false;
+        }
+
+        int interval;
+        try {
+            interval = Integer.valueOf(prefStr);
+        } catch (NumberFormatException ignored) {
+            loge("invalid alert reminder interval preference: " + prefStr);
+            return false;
+        }
+
+        if (interval == 0 || (interval == 1 && !firstTime)) {
+            return false;
+        }
+        if (interval == 1) {
+            interval = 2;   // "1" = one reminder after 2 minutes
+        }
+
+        if (DBG) log("queueAlertReminder() in " + interval + " minutes");
+
+        Intent playIntent = new Intent(context, CellBroadcastAlertReminder.class);
+        playIntent.setAction(ACTION_PLAY_ALERT_REMINDER);
+        sPlayReminderIntent = PendingIntent.getService(context, 0, playIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+
+        AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        if (alarmManager == null) {
+            loge("can't get Alarm Service");
+            return false;
+        }
+
+        // remind user after 2 minutes or 15 minutes
+        long triggerTime = SystemClock.elapsedRealtime() + (interval * 60000);
+        alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime, sPlayReminderIntent);
+        return true;
+    }
+
+    /**
+     * Stops alert reminder and cancels any queued reminders.
+     */
+    static void cancelAlertReminder() {
+        if (DBG) log("cancelAlertReminder()");
+        if (sPlayReminderRingtone != null) {
+            if (DBG) log("stopping play reminder ringtone");
+            sPlayReminderRingtone.stop();
+            sPlayReminderRingtone = null;
+        }
+        if (sPlayReminderIntent != null) {
+            if (DBG) log("canceling pending play reminder intent");
+            sPlayReminderIntent.cancel();
+            sPlayReminderIntent = null;
+        }
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+
+    private static void loge(String msg) {
+        Log.e(TAG, msg);
+    }
+}
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java b/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
index 20c2625..8513331 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastSettings.java
@@ -27,7 +27,6 @@
 import android.preference.PreferenceScreen;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
-import android.util.Log;
 
 /**
  * Settings activity for the cell broadcast receiver.
@@ -85,6 +84,9 @@
     // Preference key for initial opt-in/opt-out dialog.
     public static final String KEY_SHOW_CMAS_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
 
+    // Alert reminder interval ("once" = single 2 minute reminder).
+    public static final String KEY_ALERT_REMINDER_INTERVAL = "alert_reminder_interval";
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -129,6 +131,19 @@
             PreferenceCategory alertCategory = (PreferenceCategory)
                     findPreference(KEY_CATEGORY_ALERT_SETTINGS);
 
+            // alert reminder interval
+            ListPreference interval = (ListPreference) findPreference(KEY_ALERT_REMINDER_INTERVAL);
+            interval.setSummary(interval.getEntry());
+            interval.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference pref, Object newValue) {
+                    final ListPreference listPref = (ListPreference) pref;
+                    final int idx = listPref.findIndexOfValue((String) newValue);
+                    listPref.setSummary(listPref.getEntries()[idx]);
+                    return true;
+                }
+            });
+
             // Show alert settings and ETWS categories for ETWS builds and developer mode.
             if (enableDevSettings || showEtwsSettings) {
                 // enable/disable all alerts