1 /*
2  * Copyright (C) 2020 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.power;
18 
19 import static org.junit.Assert.assertNotNull;
20 import static org.junit.Assert.assertNull;
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyInt;
23 import static org.mockito.ArgumentMatchers.anyString;
24 import static org.mockito.ArgumentMatchers.eq;
25 import static org.mockito.Mockito.clearInvocations;
26 import static org.mockito.Mockito.never;
27 import static org.mockito.Mockito.spy;
28 import static org.mockito.Mockito.times;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.verifyNoMoreInteractions;
31 import static org.mockito.Mockito.verifyZeroInteractions;
32 import static org.mockito.Mockito.when;
33 
34 import android.content.Context;
35 import android.content.res.Resources;
36 import android.hardware.SensorManager;
37 import android.hardware.display.AmbientDisplayConfiguration;
38 import android.os.BatteryStats;
39 import android.os.Handler;
40 import android.os.IWakeLockCallback;
41 import android.os.Looper;
42 import android.os.PowerManager;
43 import android.os.RemoteException;
44 import android.os.ServiceManager;
45 import android.os.VibrationAttributes;
46 import android.os.Vibrator;
47 import android.os.test.TestLooper;
48 import android.provider.Settings;
49 import android.testing.TestableContext;
50 
51 import androidx.test.InstrumentationRegistry;
52 
53 import com.android.internal.app.IBatteryStats;
54 import com.android.server.LocalServices;
55 import com.android.server.policy.WindowManagerPolicy;
56 import com.android.server.power.batterysaver.BatterySaverStateMachine;
57 import com.android.server.power.feature.PowerManagerFlags;
58 import com.android.server.statusbar.StatusBarManagerInternal;
59 
60 import org.junit.Before;
61 import org.junit.Test;
62 import org.mockito.Mock;
63 import org.mockito.MockitoAnnotations;
64 
65 import java.util.concurrent.Executor;
66 
67 /**
68  * Tests for {@link com.android.server.power.Notifier}
69  */
70 public class NotifierTest {
71     private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
72     private static final int USER_ID = 0;
73 
74     @Mock private BatterySaverStateMachine mBatterySaverStateMachineMock;
75     @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
76     @Mock private Notifier mNotifierMock;
77     @Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
78     @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
79     @Mock private SystemPropertiesWrapper mSystemPropertiesMock;
80     @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
81     @Mock private Vibrator mVibrator;
82     @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
83     @Mock private WakeLockLog mWakeLockLog;
84 
85     @Mock private PowerManagerFlags mPowerManagerFlags;
86 
87     private PowerManagerService mService;
88     private Context mContextSpy;
89     private Resources mResourcesSpy;
90     private TestLooper mTestLooper = new TestLooper();
91     private FakeExecutor mTestExecutor = new FakeExecutor();
92     private Notifier mNotifier;
93 
94     @Before
setUp()95     public void setUp() {
96         MockitoAnnotations.initMocks(this);
97 
98         LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
99         LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
100 
101         mContextSpy = spy(new TestableContext(InstrumentationRegistry.getContext()));
102         mResourcesSpy = spy(mContextSpy.getResources());
103         when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
104         when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
105         when(mContextSpy.getSystemService(Vibrator.class)).thenReturn(mVibrator);
106 
107         mService = new PowerManagerService(mContextSpy, mInjector);
108     }
109 
110     @Test
testVibrateEnabled_wiredCharging()111     public void testVibrateEnabled_wiredCharging() {
112         createNotifier();
113 
114         // GIVEN the charging vibration is enabled
115         enableChargingVibration(true);
116 
117         // WHEN wired charging starts
118         mNotifier.onWiredChargingStarted(USER_ID);
119         mTestLooper.dispatchAll();
120         mTestExecutor.simulateAsyncExecutionOfLastCommand();
121 
122         // THEN the device vibrates once
123         verify(mVibrator, times(1)).vibrate(anyInt(), any(), any(), any(),
124                 any(VibrationAttributes.class));
125     }
126 
127     @Test
testVibrateDisabled_wiredCharging()128     public void testVibrateDisabled_wiredCharging() {
129         createNotifier();
130 
131         // GIVEN the charging vibration is disabled
132         enableChargingVibration(false);
133 
134         // WHEN wired charging starts
135         mNotifier.onWiredChargingStarted(USER_ID);
136         mTestLooper.dispatchAll();
137         mTestExecutor.simulateAsyncExecutionOfLastCommand();
138 
139         // THEN the device doesn't vibrate
140         verifyZeroInteractions(mVibrator);
141     }
142 
143     @Test
testVibrateEnabled_wirelessCharging()144     public void testVibrateEnabled_wirelessCharging() {
145         createNotifier();
146 
147         // GIVEN the charging vibration is enabled
148         enableChargingVibration(true);
149 
150         // WHEN wireless charging starts
151         mNotifier.onWirelessChargingStarted(5, USER_ID);
152         mTestLooper.dispatchAll();
153         mTestExecutor.simulateAsyncExecutionOfLastCommand();
154 
155         // THEN the device vibrates once
156         verify(mVibrator, times(1)).vibrate(anyInt(), any(), any(), any(),
157                 any(VibrationAttributes.class));
158     }
159 
160     @Test
testVibrateDisabled_wirelessCharging()161     public void testVibrateDisabled_wirelessCharging() {
162         createNotifier();
163 
164         // GIVEN the charging vibration is disabled
165         enableChargingVibration(false);
166 
167         // WHEN wireless charging starts
168         mNotifier.onWirelessChargingStarted(5, USER_ID);
169         mTestLooper.dispatchAll();
170         mTestExecutor.simulateAsyncExecutionOfLastCommand();
171 
172         // THEN the device doesn't vibrate
173         verifyZeroInteractions(mVibrator);
174     }
175 
176     @Test
testVibrateEnabled_dndOn()177     public void testVibrateEnabled_dndOn() {
178         createNotifier();
179 
180         // GIVEN the charging vibration is enabled but dnd is on
181         enableChargingVibration(true);
182         enableChargingFeedback(
183                 /* chargingFeedbackEnabled */ true,
184                 /* dndOn */ true);
185 
186         // WHEN wired charging starts
187         mNotifier.onWiredChargingStarted(USER_ID);
188         mTestLooper.dispatchAll();
189         mTestExecutor.simulateAsyncExecutionOfLastCommand();
190 
191         // THEN the device doesn't vibrate
192         verify(mVibrator, never()).vibrate(any(), any(VibrationAttributes.class));
193     }
194 
195     @Test
testWirelessAnimationEnabled()196     public void testWirelessAnimationEnabled() {
197         // GIVEN the wireless charging animation is enabled
198         when(mResourcesSpy.getBoolean(
199                 com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim))
200                 .thenReturn(true);
201         createNotifier();
202 
203         // WHEN wireless charging starts
204         mNotifier.onWirelessChargingStarted(5, USER_ID);
205         mTestLooper.dispatchAll();
206         mTestExecutor.simulateAsyncExecutionOfLastCommand();
207 
208         // THEN the charging animation is triggered
209         verify(mStatusBarManagerInternal, times(1)).showChargingAnimation(5);
210     }
211 
212     @Test
testWirelessAnimationDisabled()213     public void testWirelessAnimationDisabled() {
214         // GIVEN the wireless charging animation is disabled
215         when(mResourcesSpy.getBoolean(
216                 com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim))
217                 .thenReturn(false);
218         createNotifier();
219 
220         // WHEN wireless charging starts
221         mNotifier.onWirelessChargingStarted(5, USER_ID);
222         mTestLooper.dispatchAll();
223         mTestExecutor.simulateAsyncExecutionOfLastCommand();
224 
225         // THEN the charging animation never gets called
226         verify(mStatusBarManagerInternal, never()).showChargingAnimation(anyInt());
227     }
228 
229     @Test
testOnWakeLockListener_RemoteException_NoRethrow()230     public void testOnWakeLockListener_RemoteException_NoRethrow() {
231         when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
232         createNotifier();
233 
234         IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() {
235             @Override public void onStateChanged(boolean enabled) throws RemoteException {
236                 throw new RemoteException("Just testing");
237             }
238         };
239 
240         final int uid = 1234;
241         final int pid = 5678;
242         mNotifier.onWakeLockReleased(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
243                 "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
244                 exceptingCallback);
245         verifyZeroInteractions(mWakeLockLog);
246         mTestLooper.dispatchAll();
247         verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, 1);
248         mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
249                 "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
250                 exceptingCallback);
251         mNotifier.onWakeLockChanging(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
252                 "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
253                 exceptingCallback,
254                 PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag",
255                 "my.package.name", uid, pid, /* newWorkSource= */ null, /* newHistoryTag= */ null,
256                 exceptingCallback);
257         verifyNoMoreInteractions(mWakeLockLog);
258         mTestLooper.dispatchAll();
259         verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid,
260                 PowerManager.PARTIAL_WAKE_LOCK, 1);
261         // If we didn't throw, we're good!
262 
263         // Test with improveWakelockLatency flag false, hence the wakelock log will run on the same
264         // thread
265         clearInvocations(mWakeLockLog);
266         when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(false);
267 
268         mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
269                 "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
270                 exceptingCallback);
271         verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid,
272                 PowerManager.PARTIAL_WAKE_LOCK, -1);
273 
274         mNotifier.onWakeLockReleased(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag",
275                 "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null,
276                 exceptingCallback);
277         verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, -1);
278     }
279 
280     private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() {
281         @Override
282         Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
283                 SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
284                 FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector,
285                 Executor backgroundExecutor, PowerManagerFlags powerManagerFlags) {
286             return mNotifierMock;
287         }
288 
289         @Override
290         SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
291             return super.createSuspendBlocker(service, name);
292         }
293 
294         @Override
295         BatterySaverStateMachine createBatterySaverStateMachine(Object lock, Context context) {
296             return mBatterySaverStateMachineMock;
297         }
298 
299         @Override
300         PowerManagerService.NativeWrapper createNativeWrapper() {
301             return mNativeWrapperMock;
302         }
303 
304         @Override
305         WirelessChargerDetector createWirelessChargerDetector(
306                 SensorManager sensorManager, SuspendBlocker suspendBlocker, Handler handler) {
307             return mWirelessChargerDetectorMock;
308         }
309 
310         @Override
311         AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) {
312             return mAmbientDisplayConfigurationMock;
313         }
314 
315         @Override
316         InattentiveSleepWarningController createInattentiveSleepWarningController() {
317             return mInattentiveSleepWarningControllerMock;
318         }
319 
320         @Override
321         public SystemPropertiesWrapper createSystemPropertiesWrapper() {
322             return mSystemPropertiesMock;
323         }
324 
325         @Override
326         void invalidateIsInteractiveCaches() {
327             // Avoids an SELinux denial.
328         }
329     };
330 
enableChargingFeedback(boolean chargingFeedbackEnabled, boolean dndOn)331     private void enableChargingFeedback(boolean chargingFeedbackEnabled, boolean dndOn) {
332         // enable/disable charging feedback
333         Settings.Secure.putIntForUser(
334                 mContextSpy.getContentResolver(),
335                 Settings.Secure.CHARGING_SOUNDS_ENABLED,
336                 chargingFeedbackEnabled ? 1 : 0,
337                 USER_ID);
338 
339         // toggle on/off dnd
340         Settings.Global.putInt(
341                 mContextSpy.getContentResolver(),
342                 Settings.Global.ZEN_MODE,
343                 dndOn ? Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
344                         : Settings.Global.ZEN_MODE_OFF);
345     }
346 
enableChargingVibration(boolean enable)347     private void enableChargingVibration(boolean enable) {
348         enableChargingFeedback(true, false);
349 
350         Settings.Secure.putIntForUser(
351                 mContextSpy.getContentResolver(),
352                 Settings.Secure.CHARGING_VIBRATION_ENABLED,
353                 enable ? 1 : 0,
354                 USER_ID);
355     }
356 
createNotifier()357     private void createNotifier() {
358         Notifier.Injector injector = new Notifier.Injector() {
359             @Override
360             public long currentTimeMillis() {
361                 return 1;
362             }
363 
364             @Override
365             public WakeLockLog getWakeLockLog(Context context) {
366                 return mWakeLockLog;
367             }
368         };
369 
370         mNotifier = new Notifier(
371                 mTestLooper.getLooper(),
372                 mContextSpy,
373                 IBatteryStats.Stub.asInterface(ServiceManager.getService(
374                         BatteryStats.SERVICE_NAME)),
375                 mInjector.createSuspendBlocker(mService, "testBlocker"),
376                 null,
377                 null,
378                 null,
379                 mTestExecutor, mPowerManagerFlags, injector);
380     }
381 
382     private static class FakeExecutor implements Executor {
383         private Runnable mLastCommand;
384 
385         @Override
execute(Runnable command)386         public void execute(Runnable command) {
387             assertNull(mLastCommand);
388             assertNotNull(command);
389             mLastCommand = command;
390         }
391 
getAndResetLastCommand()392         public Runnable getAndResetLastCommand() {
393             Runnable toReturn = mLastCommand;
394             mLastCommand = null;
395             return toReturn;
396         }
397 
simulateAsyncExecutionOfLastCommand()398         public void simulateAsyncExecutionOfLastCommand() {
399             Runnable toRun = getAndResetLastCommand();
400             if (toRun != null) {
401                 toRun.run();
402             }
403         }
404     }
405 
406 }
407