blob: 397770bec822d462447a91d04fcc508d9d9f4972 [file] [log] [blame]
/*
* Copyright (C) 2021 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.server.power.hint;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.os.IHintSession;
import android.os.Process;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.power.hint.HintManagerService.AppHintSession;
import com.android.server.power.hint.HintManagerService.Injector;
import com.android.server.power.hint.HintManagerService.NativeWrapper;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* Tests for {@link com.android.server.power.hint.HintManagerService}.
*
* Build/Install/Run:
* atest FrameworksServicesTests:HintManagerServiceTest
*/
public class HintManagerServiceTest {
private static final long DEFAULT_HINT_PREFERRED_RATE = 16666666L;
private static final long DEFAULT_TARGET_DURATION = 16666666L;
private static final int UID = Process.myUid();
private static final int TID = Process.myPid();
private static final int TGID = Process.getThreadGroupLeader(TID);
private static final int[] SESSION_TIDS_A = new int[] {TID};
private static final int[] SESSION_TIDS_B = new int[] {TID};
private static final long[] DURATIONS_THREE = new long[] {1L, 100L, 1000L};
private static final long[] TIMESTAMPS_THREE = new long[] {1L, 2L, 3L};
private static final long[] DURATIONS_ZERO = new long[] {};
private static final long[] TIMESTAMPS_ZERO = new long[] {};
private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L};
@Mock private Context mContext;
@Mock private HintManagerService.NativeWrapper mNativeWrapperMock;
@Mock private ActivityManagerInternal mAmInternalMock;
private HintManagerService mService;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mNativeWrapperMock.halGetHintSessionPreferredRate())
.thenReturn(DEFAULT_HINT_PREFERRED_RATE);
when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A),
eq(DEFAULT_TARGET_DURATION))).thenReturn(1L);
when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_B),
eq(DEFAULT_TARGET_DURATION))).thenReturn(2L);
when(mAmInternalMock.getIsolatedProcesses(anyInt())).thenReturn(null);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
}
private HintManagerService createService() {
mService = new HintManagerService(mContext, new Injector() {
NativeWrapper createNativeWrapper() {
return mNativeWrapperMock;
}
});
return mService;
}
@Test
public void testInitializeService() {
HintManagerService service = createService();
verify(mNativeWrapperMock).halInit();
assertThat(service.mHintSessionPreferredRate).isEqualTo(DEFAULT_HINT_PREFERRED_RATE);
}
@Test
public void testCreateHintSessionInvalidPid() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
// Make sure we throw exception when adding a TID doesn't belong to the processes
// In this case, we add `init` PID into the list.
assertThrows(SecurityException.class,
() -> service.getBinderServiceInstance().createHintSession(token,
new int[]{TID, 1}, DEFAULT_TARGET_DURATION));
}
@Test
public void testCreateHintSession() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
IHintSession a = service.getBinderServiceInstance().createHintSession(token,
SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
assertNotNull(a);
IHintSession b = service.getBinderServiceInstance().createHintSession(token,
SESSION_TIDS_B, DEFAULT_TARGET_DURATION);
assertNotEquals(a, b);
}
@Test
public void testPauseResumeHintSession() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
.createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
// Set session to background and calling updateHintAllowed() would invoke pause();
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
final Object sync = new Object();
FgThread.getHandler().post(() -> {
synchronized (sync) {
sync.notify();
}
});
synchronized (sync) {
sync.wait();
}
assumeFalse(a.updateHintAllowed());
verify(mNativeWrapperMock, times(1)).halPauseHintSession(anyLong());
// Set session to foreground and calling updateHintAllowed() would invoke resume();
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
FgThread.getHandler().post(() -> {
synchronized (sync) {
sync.notify();
}
});
synchronized (sync) {
sync.wait();
}
assumeTrue(a.updateHintAllowed());
verify(mNativeWrapperMock, times(1)).halResumeHintSession(anyLong());
}
@Test
public void testCloseHintSession() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
IHintSession a = service.getBinderServiceInstance().createHintSession(token,
SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
a.close();
verify(mNativeWrapperMock, times(1)).halCloseHintSession(anyLong());
}
@Test
public void testUpdateTargetWorkDuration() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
IHintSession a = service.getBinderServiceInstance().createHintSession(token,
SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
assertThrows(IllegalArgumentException.class, () -> {
a.updateTargetWorkDuration(-1L);
});
assertThrows(IllegalArgumentException.class, () -> {
a.updateTargetWorkDuration(0L);
});
a.updateTargetWorkDuration(100L);
verify(mNativeWrapperMock, times(1)).halUpdateTargetWorkDuration(anyLong(), eq(100L));
}
@Test
public void testReportActualWorkDuration() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
.createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
a.updateTargetWorkDuration(100L);
a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE);
verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(),
eq(DURATIONS_THREE), eq(TIMESTAMPS_THREE));
assertThrows(IllegalArgumentException.class, () -> {
a.reportActualWorkDuration(DURATIONS_ZERO, TIMESTAMPS_THREE);
});
assertThrows(IllegalArgumentException.class, () -> {
a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_ZERO);
});
assertThrows(IllegalArgumentException.class, () -> {
a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_TWO);
});
reset(mNativeWrapperMock);
// Set session to background, then the duration would not be updated.
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
final Object sync = new Object();
FgThread.getHandler().post(() -> {
synchronized (sync) {
sync.notify();
}
});
synchronized (sync) {
sync.wait();
}
assumeFalse(a.updateHintAllowed());
a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE);
verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
}
@Test
public void testDoHintInBackground() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
.createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
final Object sync = new Object();
FgThread.getHandler().post(() -> {
synchronized (sync) {
sync.notify();
}
});
synchronized (sync) {
sync.wait();
}
assertFalse(a.updateHintAllowed());
}
@Test
public void testDoHintInForeground() throws Exception {
HintManagerService service = createService();
IBinder token = new Binder();
AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
.createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
service.mUidObserver.onUidStateChanged(
a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
assertTrue(a.updateHintAllowed());
}
}