1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.soundtrigger;
18 
19 import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_DISABLED;
20 import static android.os.PowerManager.SOUND_TRIGGER_MODE_ALL_ENABLED;
21 import static android.os.PowerManager.SOUND_TRIGGER_MODE_CRITICAL_ONLY;
22 
23 import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState;
24 import static com.android.server.soundtrigger.DeviceStateHandler.SoundTriggerDeviceState.*;
25 
26 import static com.google.common.truth.Truth.assertThat;
27 
28 import android.os.SystemClock;
29 
30 import androidx.test.filters.FlakyTest;
31 import androidx.test.runner.AndroidJUnit4;
32 
33 import com.android.internal.annotations.GuardedBy;
34 import com.android.server.utils.EventLogger;
35 
36 import org.junit.Before;
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 
40 import java.util.concurrent.CountDownLatch;
41 import java.util.concurrent.TimeUnit;
42 
43 @RunWith(AndroidJUnit4.class)
44 public final class DeviceStateHandlerTest {
45     private final long CONFIRM_NO_EVENT_WAIT_MS = 1000;
46     // A wait substantially less than the duration we delay phone notifications by
47     private final long PHONE_DELAY_BRIEF_WAIT_MS =
48             DeviceStateHandler.CALL_INACTIVE_MSG_DELAY_MS / 4;
49 
50     private DeviceStateHandler mHandler;
51     private DeviceStateHandler.DeviceStateListener mDeviceStateCallback;
52 
53     private final Object mLock = new Object();
54 
55     @GuardedBy("mLock")
56     private SoundTriggerDeviceState mState;
57 
58     @GuardedBy("mLock")
59     private CountDownLatch mLatch;
60 
61     private EventLogger mEventLogger;
62 
63     @Before
setup()64     public void setup() {
65         // Reset the state prior to each test
66         mEventLogger = new EventLogger(256, "test logger");
67         synchronized (mLock) {
68             mLatch = new CountDownLatch(1);
69         }
70         mDeviceStateCallback =
71                 (SoundTriggerDeviceState state) -> {
72                     synchronized (mLock) {
73                         mState = state;
74                         mLatch.countDown();
75                     }
76                 };
77         mHandler = new DeviceStateHandler(Runnable::run, mEventLogger);
78         mHandler.onPhoneCallStateChanged(false);
79         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
80         mHandler.registerListener(mDeviceStateCallback);
81         try {
82             waitAndAssertState(ENABLE);
83         } catch (InterruptedException e) {
84             throw new RuntimeException(e);
85         }
86     }
87 
waitAndAssertState(SoundTriggerDeviceState state)88     private void waitAndAssertState(SoundTriggerDeviceState state) throws InterruptedException {
89         CountDownLatch latch;
90         synchronized (mLock) {
91             latch = mLatch;
92         }
93         latch.await();
94         synchronized (mLock) {
95             assertThat(mState).isEqualTo(state);
96             mLatch = new CountDownLatch(1);
97         }
98     }
99 
waitToConfirmNoEventReceived()100     private void waitToConfirmNoEventReceived() throws InterruptedException {
101         CountDownLatch latch;
102         synchronized (mLock) {
103             latch = mLatch;
104         }
105         // Check that we time out
106         assertThat(latch.await(CONFIRM_NO_EVENT_WAIT_MS, TimeUnit.MILLISECONDS)).isFalse();
107     }
108 
109     @Test
onPowerModeChangedCritical_receiveStateChange()110     public void onPowerModeChangedCritical_receiveStateChange() throws Exception {
111         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
112         waitAndAssertState(CRITICAL);
113         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
114         waitAndAssertState(ENABLE);
115     }
116 
117     @Test
onPowerModeChangedDisabled_receiveStateChange()118     public void onPowerModeChangedDisabled_receiveStateChange() throws Exception {
119         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
120         waitAndAssertState(DISABLE);
121         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
122         waitAndAssertState(ENABLE);
123     }
124 
125     @Test
onPowerModeChangedMultiple_receiveStateChange()126     public void onPowerModeChangedMultiple_receiveStateChange() throws Exception {
127         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
128         waitAndAssertState(DISABLE);
129         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
130         waitAndAssertState(CRITICAL);
131         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
132         waitAndAssertState(DISABLE);
133     }
134 
135     @Test
onPowerModeSameState_noStateChange()136     public void onPowerModeSameState_noStateChange() throws Exception {
137         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
138         waitAndAssertState(DISABLE);
139         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
140         waitToConfirmNoEventReceived();
141         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
142         waitAndAssertState(CRITICAL);
143         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
144         waitToConfirmNoEventReceived();
145         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
146         waitAndAssertState(ENABLE);
147         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
148         waitToConfirmNoEventReceived();
149     }
150 
151     @Test
onPhoneCall_receiveStateChange()152     public void onPhoneCall_receiveStateChange() throws Exception {
153         mHandler.onPhoneCallStateChanged(true);
154         waitAndAssertState(DISABLE);
155         mHandler.onPhoneCallStateChanged(false);
156         waitAndAssertState(ENABLE);
157     }
158 
159     @Test
onPhoneCall_receiveStateChangeIsDelayed()160     public void onPhoneCall_receiveStateChangeIsDelayed() throws Exception {
161         mHandler.onPhoneCallStateChanged(true);
162         waitAndAssertState(DISABLE);
163         long beforeTime = SystemClock.uptimeMillis();
164         mHandler.onPhoneCallStateChanged(false);
165         waitAndAssertState(ENABLE);
166         long afterTime = SystemClock.uptimeMillis();
167         assertThat(afterTime - beforeTime).isAtLeast(DeviceStateHandler.CALL_INACTIVE_MSG_DELAY_MS);
168     }
169 
170     @Test
onPhoneCallEnterExitEnter_receiveNoStateChange()171     public void onPhoneCallEnterExitEnter_receiveNoStateChange() throws Exception {
172         mHandler.onPhoneCallStateChanged(true);
173         waitAndAssertState(DISABLE);
174         mHandler.onPhoneCallStateChanged(false);
175         SystemClock.sleep(PHONE_DELAY_BRIEF_WAIT_MS);
176         mHandler.onPhoneCallStateChanged(true);
177         waitToConfirmNoEventReceived();
178     }
179 
180     @Test
onBatteryCallbackDuringPhoneWait_receiveStateChangeDelayed()181     public void onBatteryCallbackDuringPhoneWait_receiveStateChangeDelayed() throws Exception {
182         mHandler.onPhoneCallStateChanged(true);
183         waitAndAssertState(DISABLE);
184         mHandler.onPhoneCallStateChanged(false);
185         SystemClock.sleep(PHONE_DELAY_BRIEF_WAIT_MS);
186         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
187         waitAndAssertState(CRITICAL);
188         // Ensure we don't get an ENABLE event after
189         waitToConfirmNoEventReceived();
190     }
191 
192     @Test
onBatteryChangeWhenInPhoneCall_receiveNoStateChange()193     public void onBatteryChangeWhenInPhoneCall_receiveNoStateChange() throws Exception {
194         mHandler.onPhoneCallStateChanged(true);
195         waitAndAssertState(DISABLE);
196         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_ENABLED);
197         waitToConfirmNoEventReceived();
198         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
199         waitToConfirmNoEventReceived();
200     }
201 
202     @Test
whenBatteryCriticalChangeDuringCallAfterPhoneCall_receiveCriticalStateChange()203     public void whenBatteryCriticalChangeDuringCallAfterPhoneCall_receiveCriticalStateChange()
204             throws Exception {
205         mHandler.onPhoneCallStateChanged(true);
206         waitAndAssertState(DISABLE);
207         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
208         waitToConfirmNoEventReceived();
209         mHandler.onPhoneCallStateChanged(false);
210         waitAndAssertState(CRITICAL);
211     }
212 
213     @Test
whenBatteryDisableDuringCallAfterPhoneCallBatteryEnable_receiveStateChange()214     public void whenBatteryDisableDuringCallAfterPhoneCallBatteryEnable_receiveStateChange()
215             throws Exception {
216         mHandler.onPhoneCallStateChanged(true);
217         waitAndAssertState(DISABLE);
218         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
219         waitToConfirmNoEventReceived();
220         mHandler.onPhoneCallStateChanged(false);
221         waitToConfirmNoEventReceived();
222         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
223         waitAndAssertState(CRITICAL);
224     }
225 
226     @Test
whenPhoneCallDuringBatteryDisable_receiveNoStateChange()227     public void whenPhoneCallDuringBatteryDisable_receiveNoStateChange() throws Exception {
228         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_ALL_DISABLED);
229         waitAndAssertState(DISABLE);
230         mHandler.onPhoneCallStateChanged(true);
231         waitToConfirmNoEventReceived();
232         mHandler.onPhoneCallStateChanged(false);
233         waitToConfirmNoEventReceived();
234     }
235 
236     @Test
whenPhoneCallDuringBatteryCritical_receiveStateChange()237     public void whenPhoneCallDuringBatteryCritical_receiveStateChange() throws Exception {
238         mHandler.onPowerModeChanged(SOUND_TRIGGER_MODE_CRITICAL_ONLY);
239         waitAndAssertState(CRITICAL);
240         mHandler.onPhoneCallStateChanged(true);
241         waitAndAssertState(DISABLE);
242         mHandler.onPhoneCallStateChanged(false);
243         waitAndAssertState(CRITICAL);
244     }
245 
246     // This test could be flaky, but we want to verify that we only delay notification if
247     // we are exiting a call, NOT if we are entering a call.
248     @FlakyTest
249     @Test
whenPhoneCallReceived_receiveStateChangeFast()250     public void whenPhoneCallReceived_receiveStateChangeFast() throws Exception {
251         mHandler.onPhoneCallStateChanged(true);
252         CountDownLatch latch;
253         synchronized (mLock) {
254             latch = mLatch;
255         }
256         assertThat(latch.await(PHONE_DELAY_BRIEF_WAIT_MS, TimeUnit.MILLISECONDS)).isTrue();
257         synchronized (mLock) {
258             assertThat(mState).isEqualTo(DISABLE);
259         }
260     }
261 }
262