Merge "Add unit tests for CellBroadcastAlertAudio"
diff --git a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
index b8b9607..0159dfe 100644
--- a/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
+++ b/src/com/android/cellbroadcastreceiver/CellBroadcastAlertAudio.java
@@ -137,75 +137,13 @@
     private static final int ALERT_PAUSE_FINISHED = 1001;
     private static final int ALERT_LED_FLASH_TOGGLE = 1002;
 
-    private final Handler mHandler = new Handler(Looper.myLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case ALERT_SOUND_FINISHED:
-                    if (DBG) log("ALERT_SOUND_FINISHED");
-                    stop();     // stop alert sound
-                    // if we can speak the message text
-                    if (mMessageBody != null && mTtsEngineReady && mTtsLanguageSupported) {
-                        sendMessageDelayed(mHandler.obtainMessage(ALERT_PAUSE_FINISHED),
-                                PAUSE_DURATION_BEFORE_SPEAKING_MSEC);
-                        mState = STATE_PAUSING;
-                    } else {
-                        if (DBG) log("MessageEmpty = " + (mMessageBody == null) +
-                                ", mTtsEngineReady = " + mTtsEngineReady +
-                                ", mTtsLanguageSupported = " + mTtsLanguageSupported);
-                        stopSelf();
-                        mState = STATE_IDLE;
-                    }
-                    // Set alert reminder depending on user preference
-                    CellBroadcastAlertReminder.queueAlertReminder(getApplicationContext(), mSubId,
-                            true);
-                    break;
+    private Handler mHandler;
 
-                case ALERT_PAUSE_FINISHED:
-                    if (DBG) log("ALERT_PAUSE_FINISHED");
-                    int res = TextToSpeech.ERROR;
-                    if (mMessageBody != null && mTtsEngineReady && mTtsLanguageSupported) {
-                        if (DBG) log("Speaking broadcast text: " + mMessageBody);
-
-                        mTts.setAudioAttributes(getAlertAudioAttributes(mAlertType));
-                        res = mTts.speak(mMessageBody, 2, null, TTS_UTTERANCE_ID);
-                        mState = STATE_SPEAKING;
-                    }
-                    if (res != TextToSpeech.SUCCESS) {
-                        loge("TTS engine not ready or language not supported or speak() failed");
-                        stopSelf();
-                        mState = STATE_IDLE;
-                    }
-                    break;
-
-                case ALERT_LED_FLASH_TOGGLE:
-                    if (enableLedFlash(msg.arg1 != 0)) {
-                        sendMessageDelayed(mHandler.obtainMessage(
-                                ALERT_LED_FLASH_TOGGLE, msg.arg1 != 0 ? 0 : 1, 0),
-                                DEFAULT_LED_FLASH_INTERVAL_MSEC);
-                    }
-                    break;
-
-                default:
-                    loge("Handler received unknown message, what=" + msg.what);
-             }
-        }
-    };
-
-    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
-        @Override
-        public void onCallStateChanged(int state, String ignored) {
-            // Stop the alert sound and speech if the call state changes.
-            if (state != TelephonyManager.CALL_STATE_IDLE
-                    && state != mInitialCallState) {
-                if (DBG) log("Call interrupted. Stop CellBroadcastAlertAudio service");
-                stopSelf();
-            }
-        }
-    };
+    private PhoneStateListener mPhoneStateListener;
 
     /**
      * Callback from TTS engine after initialization.
+     *
      * @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
      */
     @Override
@@ -246,6 +184,7 @@
 
     /**
      * Callback from TTS engine.
+     *
      * @param utteranceId the identifier of the utterance.
      */
     @Override
@@ -266,6 +205,75 @@
         mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
         // Listen for incoming calls to kill the alarm.
         mTelephonyManager = ((TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE));
+        mHandler = new Handler(Looper.getMainLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case ALERT_SOUND_FINISHED:
+                        if (DBG) log("ALERT_SOUND_FINISHED");
+                        stop();     // stop alert sound
+                        // if we can speak the message text
+                        if (mMessageBody != null && mTtsEngineReady && mTtsLanguageSupported) {
+                            sendMessageDelayed(mHandler.obtainMessage(ALERT_PAUSE_FINISHED),
+                                    PAUSE_DURATION_BEFORE_SPEAKING_MSEC);
+                            mState = STATE_PAUSING;
+                        } else {
+                            if (DBG) {
+                                log("MessageEmpty = " + (mMessageBody == null)
+                                        + ", mTtsEngineReady = " + mTtsEngineReady
+                                        + ", mTtsLanguageSupported = " + mTtsLanguageSupported);
+                            }
+                            stopSelf();
+                            mState = STATE_IDLE;
+                        }
+                        // Set alert reminder depending on user preference
+                        CellBroadcastAlertReminder.queueAlertReminder(getApplicationContext(),
+                                mSubId,
+                                true);
+                        break;
+
+                    case ALERT_PAUSE_FINISHED:
+                        if (DBG) log("ALERT_PAUSE_FINISHED");
+                        int res = TextToSpeech.ERROR;
+                        if (mMessageBody != null && mTtsEngineReady && mTtsLanguageSupported) {
+                            if (DBG) log("Speaking broadcast text: " + mMessageBody);
+
+                            mTts.setAudioAttributes(getAlertAudioAttributes(mAlertType));
+                            res = mTts.speak(mMessageBody, 2, null, TTS_UTTERANCE_ID);
+                            mState = STATE_SPEAKING;
+                        }
+                        if (res != TextToSpeech.SUCCESS) {
+                            loge("TTS engine not ready or language not supported or speak() "
+                                    + "failed");
+                            stopSelf();
+                            mState = STATE_IDLE;
+                        }
+                        break;
+
+                    case ALERT_LED_FLASH_TOGGLE:
+                        if (enableLedFlash(msg.arg1 != 0)) {
+                            sendMessageDelayed(mHandler.obtainMessage(
+                                    ALERT_LED_FLASH_TOGGLE, msg.arg1 != 0 ? 0 : 1, 0),
+                                    DEFAULT_LED_FLASH_INTERVAL_MSEC);
+                        }
+                        break;
+
+                    default:
+                        loge("Handler received unknown message, what=" + msg.what);
+                }
+            }
+        };
+        mPhoneStateListener = new PhoneStateListener() {
+            @Override
+            public void onCallStateChanged(int state, String ignored) {
+                // Stop the alert sound and speech if the call state changes.
+                if (state != TelephonyManager.CALL_STATE_IDLE
+                        && state != mInitialCallState) {
+                    if (DBG) log("Call interrupted. Stop CellBroadcastAlertAudio service");
+                    stopSelf();
+                }
+            }
+        };
         mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
     }
 
@@ -385,7 +393,8 @@
 
     /**
      * Start playing the alert sound.
-     * @param alertType the alert type (e.g. default, earthquake, tsunami, etc..)
+     *
+     * @param alertType    the alert type (e.g. default, earthquake, tsunami, etc..)
      * @param patternArray the alert vibration pattern
      */
     private void playAlertTone(AlertType alertType, int[] patternArray) {
@@ -533,7 +542,6 @@
      * Turn on camera's LED
      *
      * @param on {@code true} if turned on, otherwise turned off.
-     *
      * @return {@code true} if successful, otherwise false.
      */
     private boolean enableLedFlash(boolean on) {
@@ -679,7 +687,7 @@
         if (mResetAlarmVolumeNeeded) {
             log("resetting alarm volume to back to " + mUserSetAlarmVolume);
             mAudioManager.setStreamVolume(alertType == AlertType.INFO
-                    ? AudioManager.STREAM_NOTIFICATION : AudioManager.STREAM_ALARM,
+                            ? AudioManager.STREAM_NOTIFICATION : AudioManager.STREAM_ALARM,
                     mUserSetAlarmVolume, 0);
             mResetAlarmVolumeNeeded = false;
         }
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastAlertAudioTest.java b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastAlertAudioTest.java
new file mode 100644
index 0000000..c743f99
--- /dev/null
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastAlertAudioTest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2020 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.unit;
+
+import static com.android.cellbroadcastreceiver.CellBroadcastAlertService.SHOW_NEW_ALERT_ACTION;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.os.HandlerThread;
+import android.telephony.TelephonyManager;
+
+import com.android.cellbroadcastreceiver.CellBroadcastAlertAudio;
+
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.MockitoAnnotations;
+
+public class CellBroadcastAlertAudioTest extends
+        CellBroadcastServiceTestCase<CellBroadcastAlertAudio> {
+
+    private static final String TEST_MESSAGE_BODY = "test message body";
+    private static final int[] TEST_VIBRATION_PATTERN = new int[]{0, 1, 0, 1};
+    private static final int TEST_MAX_VOLUME = 1001;
+    private static final long MAX_INIT_WAIT_MS = 5000;
+
+    private Configuration mConfiguration = new Configuration();
+    private AudioDeviceInfo[] mDevices = new AudioDeviceInfo[0];
+    private Object mLock = new Object();
+    private boolean mReady;
+
+    public CellBroadcastAlertAudioTest() {
+        super(CellBroadcastAlertAudio.class);
+    }
+
+    private class PhoneStateListenerHandler extends HandlerThread {
+
+        private Runnable mFunction;
+
+        PhoneStateListenerHandler(String name, Runnable func) {
+            super(name);
+            mFunction = func;
+        }
+
+        @Override
+        public void onLooperPrepared() {
+            mFunction.run();
+            setReady(true);
+        }
+    }
+
+    protected void waitUntilReady() {
+        synchronized (mLock) {
+            if (!mReady) {
+                try {
+                    mLock.wait(MAX_INIT_WAIT_MS);
+                } catch (InterruptedException ie) {
+                }
+
+                if (!mReady) {
+                    fail("Telephony tests failed to initialize");
+                }
+            }
+        }
+    }
+
+    protected void setReady(boolean ready) {
+        synchronized (mLock) {
+            mReady = ready;
+            mLock.notifyAll();
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        doReturn(mConfiguration).when(mResources).getConfiguration();
+        doReturn(mDevices).when(mMockedAudioManager).getDevices(anyInt());
+
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testStartService() throws Throwable {
+        PhoneStateListenerHandler phoneStateListenerHandler = new PhoneStateListenerHandler(
+                "testStartService",
+                () -> {
+                    doReturn(AudioManager.RINGER_MODE_NORMAL).when(
+                            mMockedAudioManager).getRingerMode();
+
+                    Intent intent = new Intent(mContext, CellBroadcastAlertAudio.class);
+                    intent.setAction(SHOW_NEW_ALERT_ACTION);
+                    intent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_BODY,
+                            TEST_MESSAGE_BODY);
+                    intent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_VIBRATION_PATTERN_EXTRA,
+                            TEST_VIBRATION_PATTERN);
+                    startService(intent);
+                });
+        phoneStateListenerHandler.start();
+        waitUntilReady();
+        verify(mMockedAudioManager).getRingerMode();
+        verify(mMockedVibrator).vibrate(any(), any());
+        phoneStateListenerHandler.quit();
+    }
+
+    /**
+     * If the user is currently not in a call and the override DND flag is set, the volume will be
+     * set to max.
+     */
+    public void testStartServiceNotInCallOverrideDnd() throws Throwable {
+        PhoneStateListenerHandler phoneStateListenerHandler = new PhoneStateListenerHandler(
+                "testStartServiceNotInCallOverrideDnd",
+                () -> {
+                    doReturn(AudioManager.RINGER_MODE_SILENT).when(
+                            mMockedAudioManager).getRingerMode();
+                    doReturn(TelephonyManager.CALL_STATE_IDLE).when(
+                            mMockedTelephonyManager).getCallState();
+                    doReturn(TEST_MAX_VOLUME).when(mMockedAudioManager).getStreamMaxVolume(
+                            anyInt());
+
+                    Intent intent = new Intent(mContext, CellBroadcastAlertAudio.class);
+                    intent.setAction(SHOW_NEW_ALERT_ACTION);
+                    intent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_BODY,
+                            TEST_MESSAGE_BODY);
+                    intent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_VIBRATION_PATTERN_EXTRA,
+                            TEST_VIBRATION_PATTERN);
+                    intent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_OVERRIDE_DND_EXTRA, true);
+                    startService(intent);
+                });
+        phoneStateListenerHandler.start();
+        waitUntilReady();
+        verify(mMockedAudioManager).getRingerMode();
+        verify(mMockedVibrator).vibrate(any(), any());
+        verify(mMockedTelephonyManager, atLeastOnce()).getCallState();
+        verify(mMockedAudioManager).requestAudioFocus(any(), anyInt(), anyInt());
+        verify(mMockedAudioManager).getDevices(anyInt());
+        verify(mMockedAudioManager).setStreamVolume(anyInt(), eq(TEST_MAX_VOLUME), anyInt());
+        phoneStateListenerHandler.quit();
+    }
+
+    public void testStartServiceEnableLedFlash() throws Throwable {
+        PhoneStateListenerHandler phoneStateListenerHandler = new PhoneStateListenerHandler(
+                "testStartServiceEnableLedFlash",
+                () -> {
+                    doReturn(AudioManager.RINGER_MODE_NORMAL).when(
+                            mMockedAudioManager).getRingerMode();
+                    doReturn(true).when(mResources).getBoolean(
+                            eq(com.android.cellbroadcastreceiver.R.bool.enable_led_flash));
+
+                    Intent intent = new Intent(mContext, CellBroadcastAlertAudio.class);
+                    intent.setAction(SHOW_NEW_ALERT_ACTION);
+                    intent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_BODY,
+                            TEST_MESSAGE_BODY);
+                    intent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_VIBRATION_PATTERN_EXTRA,
+                            TEST_VIBRATION_PATTERN);
+                    startService(intent);
+                });
+        phoneStateListenerHandler.start();
+        waitUntilReady();
+        // TODO(b/134400042): we can't mock CameraManager because it's final, but let's at least
+        //                    make sure the code doesn't crash. If we switch to Mockito 2 this
+        //                    will be mockable.
+        //verify(mMockedCameraManager).setTorchMode(anyString(), true);
+        phoneStateListenerHandler.quit();
+    }
+
+    public void testStartServiceSilentRinger() throws Throwable {
+        PhoneStateListenerHandler phoneStateListenerHandler = new PhoneStateListenerHandler(
+                "testStartServiceSilentRinger",
+                () -> {
+                    doReturn(AudioManager.RINGER_MODE_SILENT).when(
+                            mMockedAudioManager).getRingerMode();
+
+                    Intent intent = new Intent(mContext, CellBroadcastAlertAudio.class);
+                    intent.setAction(SHOW_NEW_ALERT_ACTION);
+                    intent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_BODY,
+                            TEST_MESSAGE_BODY);
+                    intent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_VIBRATION_PATTERN_EXTRA,
+                            TEST_VIBRATION_PATTERN);
+                    startService(intent);
+                });
+        phoneStateListenerHandler.start();
+        waitUntilReady();
+        verify(mMockedAudioManager).getRingerMode();
+        verify(mMockedVibrator, times(0)).vibrate(any(), any());
+        phoneStateListenerHandler.quit();
+    }
+
+    public void testStartServiceVibrateRinger() throws Throwable {
+        PhoneStateListenerHandler phoneStateListenerHandler = new PhoneStateListenerHandler(
+                "testStartServiceVibrateRinger",
+                () -> {
+                    doReturn(AudioManager.RINGER_MODE_VIBRATE).when(
+                            mMockedAudioManager).getRingerMode();
+
+                    Intent intent = new Intent(mContext, CellBroadcastAlertAudio.class);
+                    intent.setAction(SHOW_NEW_ALERT_ACTION);
+                    intent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_BODY,
+                            TEST_MESSAGE_BODY);
+                    intent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_VIBRATION_PATTERN_EXTRA,
+                            TEST_VIBRATION_PATTERN);
+                    startService(intent);
+                });
+        phoneStateListenerHandler.start();
+        waitUntilReady();
+        verify(mMockedAudioManager).getRingerMode();
+        verify(mMockedVibrator).vibrate(any(), any());
+        verify(mMockedVibrator).cancel();
+        phoneStateListenerHandler.quit();
+    }
+
+    /**
+     * When an alert is triggered while an alert is already happening, the system needs to stop
+     * the previous alert.
+     */
+    public void testStartServiceAndStop() throws Throwable {
+        Intent intent = new Intent(mContext, CellBroadcastAlertAudio.class);
+        intent.setAction(SHOW_NEW_ALERT_ACTION);
+        intent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_MESSAGE_BODY,
+                TEST_MESSAGE_BODY);
+        intent.putExtra(CellBroadcastAlertAudio.ALERT_AUDIO_VIBRATION_PATTERN_EXTRA,
+                TEST_VIBRATION_PATTERN);
+        doReturn(AudioManager.RINGER_MODE_NORMAL).when(
+                mMockedAudioManager).getRingerMode();
+
+        PhoneStateListenerHandler phoneStateListenerHandler = new PhoneStateListenerHandler(
+                "testStartServiceStop",
+                () -> {
+                    startService(intent);
+                    startService(intent);
+                });
+        phoneStateListenerHandler.start();
+        waitUntilReady();
+        verify(mMockedVibrator, times(2)).cancel();
+        phoneStateListenerHandler.quit();
+    }
+}
diff --git a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastServiceTestCase.java b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastServiceTestCase.java
index 94a5265..ee72f0e 100644
--- a/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastServiceTestCase.java
+++ b/tests/unit/src/com/android/cellbroadcastreceiver/unit/CellBroadcastServiceTestCase.java
@@ -28,6 +28,7 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.media.AudioManager;
+import android.os.Vibrator;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -60,6 +61,8 @@
     protected SubscriptionInfo mMockSubscriptionInfo;
     @Mock
     protected TelephonyManager mMockedTelephonyManager;
+    @Mock
+    protected Vibrator mMockedVibrator;
 
     MockedServiceManager mMockedServiceManager;
 
@@ -118,6 +121,8 @@
                     return mMockedSubscriptionManager;
                 case Context.TELEPHONY_SERVICE:
                     return mMockedTelephonyManager;
+                case Context.VIBRATOR_SERVICE:
+                    return mMockedVibrator;
             }
             return super.getSystemService(name);
         }
@@ -130,7 +135,8 @@
         // CellBroadcastSettings.getResources(context).
         doReturn(mSubService).when(mSubService).queryLocalInterface(anyString());
         doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mSubService).getDefaultSubId();
-        doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(mSubService).getDefaultSmsSubId();
+        doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID).when(
+                mSubService).getDefaultSmsSubId();
 
         doReturn(new String[]{""}).when(mResources).getStringArray(anyInt());