1 /*
2  * Copyright (C) 2021 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.hint;
18 
19 
20 import static com.android.server.power.hint.HintManagerService.CLEAN_UP_UID_DELAY_MILLIS;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import static org.junit.Assert.assertArrayEquals;
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertFalse;
27 import static org.junit.Assert.assertNotEquals;
28 import static org.junit.Assert.assertNotNull;
29 import static org.junit.Assert.assertNull;
30 import static org.junit.Assert.assertThrows;
31 import static org.junit.Assert.assertTrue;
32 import static org.mockito.Mockito.any;
33 import static org.mockito.Mockito.anyBoolean;
34 import static org.mockito.Mockito.anyInt;
35 import static org.mockito.Mockito.anyLong;
36 import static org.mockito.Mockito.clearInvocations;
37 import static org.mockito.Mockito.doThrow;
38 import static org.mockito.Mockito.eq;
39 import static org.mockito.Mockito.never;
40 import static org.mockito.Mockito.reset;
41 import static org.mockito.Mockito.times;
42 import static org.mockito.Mockito.verify;
43 import static org.mockito.Mockito.when;
44 
45 import android.annotation.NonNull;
46 import android.app.ActivityManager;
47 import android.app.ActivityManagerInternal;
48 import android.content.Context;
49 import android.content.pm.ApplicationInfo;
50 import android.content.pm.PackageManager;
51 import android.hardware.common.fmq.MQDescriptor;
52 import android.hardware.power.ChannelConfig;
53 import android.hardware.power.ChannelMessage;
54 import android.hardware.power.CpuHeadroomParams;
55 import android.hardware.power.CpuHeadroomResult;
56 import android.hardware.power.GpuHeadroomParams;
57 import android.hardware.power.GpuHeadroomResult;
58 import android.hardware.power.IPower;
59 import android.hardware.power.SessionConfig;
60 import android.hardware.power.SessionTag;
61 import android.hardware.power.SupportInfo;
62 import android.hardware.power.WorkDuration;
63 import android.os.Binder;
64 import android.os.CpuHeadroomParamsInternal;
65 import android.os.GpuHeadroomParamsInternal;
66 import android.os.IBinder;
67 import android.os.IHintSession;
68 import android.os.PerformanceHintManager;
69 import android.os.Process;
70 import android.os.RemoteException;
71 import android.os.SessionCreationConfig;
72 import android.platform.test.annotations.RequiresFlagsEnabled;
73 import android.platform.test.flag.junit.CheckFlagsRule;
74 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
75 import android.util.Log;
76 
77 import com.android.server.FgThread;
78 import com.android.server.LocalServices;
79 import com.android.server.power.hint.HintManagerService.AppHintSession;
80 import com.android.server.power.hint.HintManagerService.Injector;
81 import com.android.server.power.hint.HintManagerService.NativeWrapper;
82 
83 import org.junit.Before;
84 import org.junit.Rule;
85 import org.junit.Test;
86 import org.mockito.Mock;
87 import org.mockito.MockitoAnnotations;
88 import org.mockito.invocation.InvocationOnMock;
89 import org.mockito.stubbing.Answer;
90 
91 import java.util.ArrayList;
92 import java.util.Collections;
93 import java.util.List;
94 import java.util.concurrent.CountDownLatch;
95 import java.util.concurrent.TimeUnit;
96 import java.util.concurrent.atomic.AtomicInteger;
97 import java.util.concurrent.atomic.AtomicReference;
98 import java.util.concurrent.locks.LockSupport;
99 
100 /**
101  * Tests for {@link com.android.server.power.hint.HintManagerService}.
102  *
103  * Build/Install/Run:
104  * atest FrameworksServicesTests:HintManagerServiceTest
105  */
106 public class HintManagerServiceTest {
107     private static final String TAG = "HintManagerServiceTest";
108 
makeWorkDuration( long timestamp, long duration, long workPeriodStartTime, long cpuDuration, long gpuDuration)109     private static WorkDuration makeWorkDuration(
110             long timestamp, long duration, long workPeriodStartTime,
111             long cpuDuration, long gpuDuration) {
112         WorkDuration out = new WorkDuration();
113         out.timeStampNanos = timestamp;
114         out.durationNanos = duration;
115         out.workPeriodStartTimestampNanos = workPeriodStartTime;
116         out.cpuDurationNanos = cpuDuration;
117         out.gpuDurationNanos = gpuDuration;
118         return out;
119     }
120 
121     private static final long DEFAULT_HINT_PREFERRED_RATE = 16666666L;
122     private static final long DEFAULT_TARGET_DURATION = 16666666L;
123     private static final long DOUBLED_TARGET_DURATION = 33333333L;
124     private static final long CONCURRENCY_TEST_DURATION_SEC = 10;
125     private static final int UID = Process.myUid();
126     private static final int TID = Process.myPid();
127     private static final int TGID = Process.getThreadGroupLeader(TID);
128     private static final int[] SESSION_TIDS_A = new int[] {TID};
129     private static final int[] SESSION_TIDS_B = new int[] {TID};
130     private static final int[] SESSION_TIDS_C = new int[] {TID};
131     private static final long[] DURATIONS_THREE = new long[] {1L, 100L, 1000L};
132     private static final long[] TIMESTAMPS_THREE = new long[] {1L, 2L, 3L};
133     private static final long[] SESSION_PTRS = new long[] {11L, 22L, 33L};
134     private static final long[] SESSION_IDS = new long[] {1L, 11L, 111L};
135     private static final long[] DURATIONS_ZERO = new long[] {};
136     private static final long[] TIMESTAMPS_ZERO = new long[] {};
137     private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L};
138     private static final WorkDuration[] WORK_DURATIONS_FIVE = new WorkDuration[] {
139             makeWorkDuration(1L, 11L, 1L, 8L, 4L),
140             makeWorkDuration(2L, 13L, 2L, 8L, 6L),
141             makeWorkDuration(3L, 333333333L, 3L, 8L, 333333333L),
142             makeWorkDuration(2L, 13L, 2L, 0L, 6L),
143             makeWorkDuration(2L, 13L, 2L, 8L, 0L),
144     };
145     private static final String TEST_APP_NAME = "com.android.test.app";
146 
147     @Mock
148     private Context mContext;
149     @Mock
150     private HintManagerService.NativeWrapper mNativeWrapperMock;
151     @Mock
152     private IPower mIPowerMock;
153     @Mock
154     private ActivityManagerInternal mAmInternalMock;
155     @Mock
156     private PackageManager mMockPackageManager;
157     @Rule
158     public final CheckFlagsRule mCheckFlagsRule =
159             DeviceFlagsValueProvider.createCheckFlagsRule();
160 
161     private HintManagerService mService;
162     private ChannelConfig mConfig;
163     private SupportInfo mSupportInfo;
164 
fakeCreateWithConfig(Long ptr, Long sessionId)165     private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) {
166         return new Answer<Long>() {
167             public Long answer(InvocationOnMock invocation) {
168                 ((SessionConfig) invocation.getArguments()[5]).id = sessionId;
169                 return ptr;
170             }
171         };
172     }
173 
174     @Before
175     public void setUp() throws Exception {
176         MockitoAnnotations.initMocks(this);
177         mConfig = new ChannelConfig();
178         mConfig.readFlagBitmask = 1;
179         mConfig.writeFlagBitmask = 2;
180         mConfig.channelDescriptor = new MQDescriptor<ChannelMessage, Byte>();
181         mConfig.eventFlagDescriptor = new MQDescriptor<Byte, Byte>();
182         ApplicationInfo applicationInfo = new ApplicationInfo();
183         applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
184         mSupportInfo = new SupportInfo();
185         mSupportInfo.headroom = new SupportInfo.HeadroomSupportInfo();
186         mSupportInfo.headroom.isCpuSupported = true;
187         mSupportInfo.headroom.cpuMinIntervalMillis = 2000;
188         mSupportInfo.headroom.isGpuSupported = true;
189         mSupportInfo.headroom.gpuMinIntervalMillis = 2000;
190         when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
191         when(mMockPackageManager.getNameForUid(anyInt())).thenReturn(TEST_APP_NAME);
192         when(mMockPackageManager.getApplicationInfo(eq(TEST_APP_NAME), anyInt()))
193                 .thenReturn(applicationInfo);
194         when(mNativeWrapperMock.halGetHintSessionPreferredRate())
195                 .thenReturn(DEFAULT_HINT_PREFERRED_RATE);
196         when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A),
197                 eq(DEFAULT_TARGET_DURATION))).thenReturn(SESSION_PTRS[0]);
198         when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_B),
199                 eq(DOUBLED_TARGET_DURATION))).thenReturn(SESSION_PTRS[1]);
200         when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_C),
201                 eq(0L))).thenReturn(SESSION_PTRS[2]);
202         when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID),
203                 eq(SESSION_TIDS_A), eq(DEFAULT_TARGET_DURATION), anyInt(),
204                 any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[0],
205                 SESSION_IDS[0]));
206         when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID),
207                 eq(SESSION_TIDS_B), eq(DOUBLED_TARGET_DURATION), anyInt(),
208                 any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[1],
209                 SESSION_IDS[1]));
210         when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID),
211                 eq(SESSION_TIDS_C), eq(0L), anyInt(),
212                 any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[2],
213                     SESSION_IDS[2]));
214 
215         when(mIPowerMock.getInterfaceVersion()).thenReturn(6);
216         when(mIPowerMock.getSupportInfo()).thenReturn(mSupportInfo);
217         when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig);
218         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
219         LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
220     }
221 
222     /**
223      * Mocks the creation calls, but without support for new createHintSessionWithConfig method
224      */
225     public void makeConfigCreationUnsupported() {
226         reset(mNativeWrapperMock);
227         when(mNativeWrapperMock.halGetHintSessionPreferredRate())
228                 .thenReturn(DEFAULT_HINT_PREFERRED_RATE);
229         when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A),
230                 eq(DEFAULT_TARGET_DURATION))).thenReturn(SESSION_PTRS[0]);
231         when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_B),
232                 eq(DOUBLED_TARGET_DURATION))).thenReturn(SESSION_PTRS[1]);
233         when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_C),
234                 eq(0L))).thenReturn(SESSION_PTRS[2]);
235         when(mNativeWrapperMock.halCreateHintSessionWithConfig(anyInt(), anyInt(),
236                 any(int[].class), anyLong(), anyInt(),
237                 any(SessionConfig.class))).thenThrow(new UnsupportedOperationException());
238     }
239 
240     static class NativeWrapperFake extends NativeWrapper {
241         @Override
242         public void halInit() {
243         }
244 
245         @Override
246         public long halGetHintSessionPreferredRate() {
247             return 1;
248         }
249 
250         @Override
251         public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) {
252             return 1;
253         }
254 
255         @Override
256         public long halCreateHintSessionWithConfig(int tgid, int uid, int[] tids,
257                 long durationNanos, int tag, SessionConfig config) {
258             return 1;
259         }
260 
261         @Override
262         public void halPauseHintSession(long halPtr) {
263         }
264 
265         @Override
266         public void halResumeHintSession(long halPtr) {
267         }
268 
269         @Override
270         public void halCloseHintSession(long halPtr) {
271         }
272 
273         @Override
274         public void halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos) {
275         }
276 
277         @Override
278         public void halReportActualWorkDuration(
279                 long halPtr, long[] actualDurationNanos, long[] timeStampNanos) {
280         }
281 
282         @Override
283         public void halSendHint(long halPtr, int hint) {
284         }
285 
286         @Override
287         public void halSetThreads(long halPtr, int[] tids) {
288         }
289 
290         @Override
291         public void halSetMode(long halPtr, int mode, boolean enabled) {
292         }
293     }
294 
295     private HintManagerService createService() {
296         mService = new HintManagerService(mContext, new Injector() {
297             NativeWrapper createNativeWrapper() {
298                 return mNativeWrapperMock;
299             }
300             IPower createIPower() {
301                 return mIPowerMock;
302             }
303         });
304         return mService;
305     }
306 
307     private HintManagerService createServiceWithFakeWrapper() {
308         mService = new HintManagerService(mContext, new Injector() {
309             NativeWrapper createNativeWrapper() {
310                 return new NativeWrapperFake();
311             }
312             IPower createIPower() {
313                 return mIPowerMock;
314             }
315         });
316         return mService;
317     }
318 
319     private SessionCreationConfig makeSessionCreationConfig(int[] tids,
320             long targetWorkDurationNanos) {
321         SessionCreationConfig config = new SessionCreationConfig();
322         config.tids = tids;
323         config.targetWorkDurationNanos = targetWorkDurationNanos;
324         return config;
325     }
326 
327     @Test
328     public void testInitializeService() {
329         HintManagerService service = createService();
330         verify(mNativeWrapperMock).halInit();
331         assertThat(service.mHintSessionPreferredRate).isEqualTo(DEFAULT_HINT_PREFERRED_RATE);
332     }
333 
334     @Test
335     public void testCreateHintSessionInvalidPid() throws Exception {
336         HintManagerService service = createService();
337         IBinder token = new Binder();
338         SessionCreationConfig creationConfig =
339                 makeSessionCreationConfig(new int[]{TID, 1}, DEFAULT_TARGET_DURATION);
340         // Make sure we throw exception when adding a TID doesn't belong to the processes
341         // In this case, we add `init` PID into the list.
342         SessionConfig config = new SessionConfig();
343         assertThrows(SecurityException.class,
344                 () -> service.getBinderServiceInstance().createHintSessionWithConfig(token,
345                         SessionTag.OTHER, creationConfig, config));
346     }
347 
348     @Test
349     public void testCreateHintSessionFallback() throws Exception {
350         HintManagerService service = createService();
351         IBinder token = new Binder();
352         makeConfigCreationUnsupported();
353         SessionCreationConfig creationConfig =
354                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
355 
356         IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token,
357                 SessionTag.OTHER, creationConfig, new SessionConfig());
358         assertNotNull(a);
359 
360         creationConfig.tids = SESSION_TIDS_B;
361         creationConfig.targetWorkDurationNanos = DOUBLED_TARGET_DURATION;
362         IHintSession b = service.getBinderServiceInstance().createHintSessionWithConfig(token,
363                 SessionTag.OTHER, creationConfig, new SessionConfig());
364         assertNotEquals(a, b);
365 
366         creationConfig.tids = SESSION_TIDS_C;
367         creationConfig.targetWorkDurationNanos = 0L;
368         IHintSession c = service.getBinderServiceInstance().createHintSessionWithConfig(token,
369                 SessionTag.OTHER, creationConfig, new SessionConfig());
370         assertNotNull(c);
371         verify(mNativeWrapperMock, times(3)).halCreateHintSession(anyInt(), anyInt(),
372                 any(int[].class), anyLong());
373     }
374 
375     @Test
376     public void testCreateHintSessionWithConfig() throws Exception {
377         HintManagerService service = createService();
378         IBinder token = new Binder();
379         SessionCreationConfig creationConfig =
380                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
381 
382         SessionConfig config = new SessionConfig();
383         IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token,
384                 SessionTag.OTHER, creationConfig, config);
385         assertNotNull(a);
386         assertEquals(SESSION_IDS[0], config.id);
387 
388         SessionConfig config2 = new SessionConfig();
389         creationConfig.tids = SESSION_TIDS_B;
390         creationConfig.targetWorkDurationNanos = DOUBLED_TARGET_DURATION;
391         IHintSession b = service.getBinderServiceInstance().createHintSessionWithConfig(token,
392                 SessionTag.APP, creationConfig, config2);
393         assertNotEquals(a, b);
394         assertEquals(SESSION_IDS[1], config2.id);
395 
396         SessionConfig config3 = new SessionConfig();
397         creationConfig.tids = SESSION_TIDS_C;
398         creationConfig.targetWorkDurationNanos = 0L;
399         IHintSession c = service.getBinderServiceInstance().createHintSessionWithConfig(token,
400                 SessionTag.GAME, creationConfig, config3);
401         assertNotNull(c);
402         assertEquals(SESSION_IDS[2], config3.id);
403         verify(mNativeWrapperMock, times(3)).halCreateHintSessionWithConfig(anyInt(), anyInt(),
404                 any(int[].class), anyLong(), anyInt(), any(SessionConfig.class));
405     }
406 
407     @Test
408     public void testCreateGraphicsPipelineSessions() throws Exception {
409         HintManagerService service = createService();
410         IBinder token = new Binder();
411 
412         final int threadCount =
413                 service.getBinderServiceInstance().getMaxGraphicsPipelineThreadsCount();
414         long sessionPtr1 = 1111L;
415         long sessionId1 = 11111L;
416         CountDownLatch stopLatch1 = new CountDownLatch(1);
417         int[] tids1 = createThreads(threadCount, stopLatch1);
418         when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), eq(tids1),
419                 eq(DEFAULT_TARGET_DURATION), anyInt(), any(SessionConfig.class)))
420                 .thenAnswer(fakeCreateWithConfig(sessionPtr1, sessionId1));
421         SessionCreationConfig creationConfig =
422                 makeSessionCreationConfig(tids1, DEFAULT_TARGET_DURATION);
423 
424         creationConfig.modesToEnable = new int[] {1}; // GRAPHICS_PIPELINE
425 
426         SessionConfig config = new SessionConfig();
427         IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token,
428                 SessionTag.OTHER, creationConfig, config);
429         assertNotNull(a);
430         assertEquals(sessionId1, config.id);
431 
432         creationConfig.tids = createThreads(1, stopLatch1);
433 
434         assertThrows(IllegalArgumentException.class, () -> {
435             service.getBinderServiceInstance().createHintSessionWithConfig(token,
436                     SessionTag.OTHER, creationConfig, config);
437         });
438     }
439 
440     @Test
441     public void testPauseResumeHintSession() throws Exception {
442         HintManagerService service = createService();
443         IBinder token = new Binder();
444         SessionCreationConfig creationConfig =
445                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
446 
447         AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
448                 .createHintSessionWithConfig(token, SessionTag.OTHER,
449                         creationConfig, new SessionConfig());
450 
451         // Set session to background and calling updateHintAllowedByProcState() would invoke
452         // pause();
453         service.mUidObserver.onUidStateChanged(
454                 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
455 
456         // Using CountDownLatch to ensure above onUidStateChanged() job was digested.
457         final CountDownLatch latch = new CountDownLatch(1);
458         FgThread.getHandler().post(() -> {
459             latch.countDown();
460         });
461         latch.await();
462         assertFalse(service.mUidObserver.isUidForeground(a.mUid));
463         verify(mNativeWrapperMock, times(1)).halPauseHintSession(anyLong());
464 
465         // Set session to foreground and calling updateHintAllowedByProcState() would invoke
466         // resume();
467         service.mUidObserver.onUidStateChanged(
468                 a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
469 
470         // Using CountDownLatch to ensure above onUidStateChanged() job was digested.
471         final CountDownLatch latch2 = new CountDownLatch(1);
472         FgThread.getHandler().post(() -> {
473             latch2.countDown();
474         });
475         latch2.await();
476 
477         assertTrue(service.mUidObserver.isUidForeground(a.mUid));
478         verify(mNativeWrapperMock, times(1)).halResumeHintSession(anyLong());
479     }
480 
481     @Test
482     public void testCloseHintSession() throws Exception {
483         HintManagerService service = createService();
484         IBinder token = new Binder();
485         SessionCreationConfig creationConfig =
486                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
487 
488         IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token,
489                 SessionTag.OTHER, creationConfig, new SessionConfig());
490 
491         a.close();
492         verify(mNativeWrapperMock, times(1)).halCloseHintSession(anyLong());
493     }
494 
495     @Test
496     public void testUpdateTargetWorkDuration() throws Exception {
497         HintManagerService service = createService();
498         IBinder token = new Binder();
499         SessionCreationConfig creationConfig =
500                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
501 
502         IHintSession a = service.getBinderServiceInstance().createHintSessionWithConfig(token,
503                 SessionTag.OTHER, creationConfig, new SessionConfig());
504 
505         assertThrows(IllegalArgumentException.class, () -> {
506             a.updateTargetWorkDuration(-1L);
507         });
508 
509         assertThrows(IllegalArgumentException.class, () -> {
510             a.updateTargetWorkDuration(0L);
511         });
512 
513         a.updateTargetWorkDuration(100L);
514         verify(mNativeWrapperMock, times(1)).halUpdateTargetWorkDuration(anyLong(), eq(100L));
515     }
516 
517     @Test
518     public void testReportActualWorkDuration() throws Exception {
519         HintManagerService service = createService();
520         IBinder token = new Binder();
521         SessionCreationConfig creationConfig =
522                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
523 
524         AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
525                 .createHintSessionWithConfig(token, SessionTag.OTHER,
526                         creationConfig, new SessionConfig());
527 
528         a.updateTargetWorkDuration(100L);
529         a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE);
530         verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(),
531                 eq(DURATIONS_THREE), eq(TIMESTAMPS_THREE));
532 
533         assertThrows(IllegalArgumentException.class, () -> {
534             a.reportActualWorkDuration(DURATIONS_ZERO, TIMESTAMPS_THREE);
535         });
536 
537         assertThrows(IllegalArgumentException.class, () -> {
538             a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_ZERO);
539         });
540 
541         assertThrows(IllegalArgumentException.class, () -> {
542             a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_TWO);
543         });
544 
545         reset(mNativeWrapperMock);
546         // Set session to background, then the duration would not be updated.
547         service.mUidObserver.onUidStateChanged(
548                 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
549 
550         // Using CountDownLatch to ensure above onUidStateChanged() job was digested.
551         final CountDownLatch latch = new CountDownLatch(1);
552         FgThread.getHandler().post(() -> {
553             latch.countDown();
554         });
555         latch.await();
556 
557         assertFalse(service.mUidObserver.isUidForeground(a.mUid));
558         a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE);
559         verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
560     }
561 
562     @Test
563     public void testSendHint() throws Exception {
564         HintManagerService service = createService();
565         IBinder token = new Binder();
566         SessionCreationConfig creationConfig =
567                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
568 
569         AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
570                 .createHintSessionWithConfig(token, SessionTag.OTHER,
571                         creationConfig, new SessionConfig());
572 
573         a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
574         verify(mNativeWrapperMock, times(1)).halSendHint(anyLong(),
575                 eq(PerformanceHintManager.Session.CPU_LOAD_RESET));
576 
577         assertThrows(IllegalArgumentException.class, () -> {
578             a.sendHint(-1);
579         });
580 
581         reset(mNativeWrapperMock);
582         // Set session to background, then the duration would not be updated.
583         service.mUidObserver.onUidStateChanged(
584                 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
585         FgThread.getHandler().runWithScissors(() -> { }, 500);
586         assertFalse(service.mUidObserver.isUidForeground(a.mUid));
587         a.sendHint(PerformanceHintManager.Session.CPU_LOAD_RESET);
588         verify(mNativeWrapperMock, never()).halSendHint(anyLong(), anyInt());
589     }
590 
591     @Test
592     public void testDoHintInBackground() throws Exception {
593         HintManagerService service = createService();
594         IBinder token = new Binder();
595         SessionCreationConfig creationConfig =
596                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
597 
598         AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
599                 .createHintSessionWithConfig(token, SessionTag.OTHER,
600                         creationConfig, new SessionConfig());
601 
602         service.mUidObserver.onUidStateChanged(
603                 a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
604 
605         // Using CountDownLatch to ensure above onUidStateChanged() job was digested.
606         final CountDownLatch latch = new CountDownLatch(1);
607         FgThread.getHandler().post(() -> {
608             latch.countDown();
609         });
610         latch.await();
611 
612         assertFalse(service.mUidObserver.isUidForeground(a.mUid));
613     }
614 
615     @Test
616     public void testDoHintInForeground() throws Exception {
617         HintManagerService service = createService();
618         IBinder token = new Binder();
619         SessionCreationConfig creationConfig =
620                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
621 
622         AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
623                 .createHintSessionWithConfig(token, SessionTag.OTHER,
624                         creationConfig, new SessionConfig());
625 
626         service.mUidObserver.onUidStateChanged(
627                 a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
628         assertTrue(service.mUidObserver.isUidForeground(a.mUid));
629     }
630 
631     @Test
632     public void testSetThreads() throws Exception {
633         HintManagerService service = createService();
634         IBinder token = new Binder();
635         SessionCreationConfig creationConfig =
636                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
637 
638         AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
639                 .createHintSessionWithConfig(token, SessionTag.OTHER,
640                         creationConfig, new SessionConfig());
641 
642         a.updateTargetWorkDuration(100L);
643 
644         assertThrows(IllegalArgumentException.class, () -> {
645             a.setThreads(new int[]{});
646         });
647 
648         a.setThreads(SESSION_TIDS_B);
649         verify(mNativeWrapperMock, times(1)).halSetThreads(anyLong(), eq(SESSION_TIDS_B));
650         assertArrayEquals(SESSION_TIDS_B, a.getThreadIds());
651 
652         reset(mNativeWrapperMock);
653         // Set session to background, then the duration would not be updated.
654         service.mUidObserver.onUidStateChanged(
655                 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
656         FgThread.getHandler().runWithScissors(() -> { }, 500);
657         assertFalse(service.mUidObserver.isUidForeground(a.mUid));
658         a.setThreads(SESSION_TIDS_A);
659         verify(mNativeWrapperMock, never()).halSetThreads(anyLong(), any());
660     }
661 
662     @Test
663     @RequiresFlagsEnabled(Flags.FLAG_POWERHINT_THREAD_CLEANUP)
664     public void testNoCleanupDeadThreadsForPrevPowerHalVersion() throws Exception {
665         reset(mIPowerMock);
666         when(mIPowerMock.getInterfaceVersion()).thenReturn(3);
667         HintManagerService service = createService();
668         IBinder token = new Binder();
669         int threadCount = 2;
670         long sessionPtr1 = 111;
671         CountDownLatch stopLatch1 = new CountDownLatch(1);
672         int[] tids1 = createThreads(threadCount, stopLatch1);
673         when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), eq(tids1),
674                 eq(DEFAULT_TARGET_DURATION), anyInt(), any(SessionConfig.class)))
675                 .thenReturn(sessionPtr1);
676         SessionCreationConfig creationConfig =
677                 makeSessionCreationConfig(tids1, DEFAULT_TARGET_DURATION);
678         AppHintSession session1 = (AppHintSession) service.getBinderServiceInstance()
679                 .createHintSessionWithConfig(token, SessionTag.OTHER,
680                         creationConfig, new SessionConfig());
681         assertNotNull(session1);
682 
683         // trigger UID state change by making the process foreground->background, but because the
684         // power hal version is too low, this should result in no cleanup as setThreads don't fire.
685         service.mUidObserver.onUidStateChanged(UID,
686                 ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
687         service.mUidObserver.onUidStateChanged(UID,
688                 ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
689         LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000) + TimeUnit.MILLISECONDS.toNanos(
690                 CLEAN_UP_UID_DELAY_MILLIS));
691         verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any());
692         reset(mNativeWrapperMock);
693         // this should resume but not update the threads as no cleanup was performed
694         service.mUidObserver.onUidStateChanged(UID,
695                 ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
696         verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any());
697     }
698 
699 
700     @Test
701     @RequiresFlagsEnabled(Flags.FLAG_POWERHINT_THREAD_CLEANUP)
702     public void testCleanupDeadThreads() throws Exception {
703         HintManagerService service = createService();
704         IBinder token = new Binder();
705         int threadCount = 2;
706 
707         long sessionPtr1 = 111;
708         CountDownLatch stopLatch1 = new CountDownLatch(1);
709         int[] tids1 = createThreads(threadCount, stopLatch1);
710         when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), eq(tids1),
711                 eq(DEFAULT_TARGET_DURATION), anyInt(), any(SessionConfig.class)))
712                 .thenReturn(sessionPtr1);
713         SessionCreationConfig creationConfig =
714                 makeSessionCreationConfig(tids1, DEFAULT_TARGET_DURATION);
715         AppHintSession session1 = (AppHintSession) service.getBinderServiceInstance()
716                 .createHintSessionWithConfig(token, SessionTag.OTHER,
717                         creationConfig, new SessionConfig());
718         assertNotNull(session1);
719 
720         // let all session 1 threads to exit and the cleanup should force pause the session 1
721         stopLatch1.countDown();
722         LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
723         service.mUidObserver.onUidStateChanged(UID,
724                 ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
725         service.mUidObserver.onUidStateChanged(UID,
726                 ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
727         LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000) + TimeUnit.MILLISECONDS.toNanos(
728                 CLEAN_UP_UID_DELAY_MILLIS));
729         verify(mNativeWrapperMock, times(1)).halPauseHintSession(eq(sessionPtr1));
730         verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any());
731         verifyAllHintsEnabled(session1, false);
732         service.mUidObserver.onUidStateChanged(UID,
733                 ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
734         LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000));
735         assertArrayEquals(tids1, session1.getTidsInternal());
736         verifyAllHintsEnabled(session1, false);
737         reset(mNativeWrapperMock);
738 
739         // in foreground, set new tids for session 1 then it should be resumed and all hints allowed
740         stopLatch1 = new CountDownLatch(1);
741         tids1 = createThreads(threadCount, stopLatch1);
742         session1.setThreads(tids1);
743         verify(mNativeWrapperMock, times(1)).halSetThreads(eq(sessionPtr1), eq(tids1));
744         verify(mNativeWrapperMock, times(1)).halResumeHintSession(eq(sessionPtr1));
745         verifyAllHintsEnabled(session1, true);
746         reset(mNativeWrapperMock);
747 
748         // let all session 1 and 2 non isolated threads to exit
749         stopLatch1.countDown();
750         LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
751         service.mUidObserver.onUidStateChanged(UID,
752                 ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0);
753         LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000) + TimeUnit.MILLISECONDS.toNanos(
754                 CLEAN_UP_UID_DELAY_MILLIS));
755         verify(mNativeWrapperMock, times(1)).halPauseHintSession(eq(sessionPtr1));
756         verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any());
757         // in background, set threads for session 1 then it should not be force paused next time
758         session1.setThreads(SESSION_TIDS_A);
759         // the new TIDs pending list should be updated
760         assertArrayEquals(SESSION_TIDS_A, session1.getTidsInternal());
761         verifyAllHintsEnabled(session1, false);
762         reset(mNativeWrapperMock);
763 
764         service.mUidObserver.onUidStateChanged(UID,
765                 ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
766         LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000) + TimeUnit.MILLISECONDS.toNanos(
767                 CLEAN_UP_UID_DELAY_MILLIS));
768         verify(mNativeWrapperMock, times(1)).halSetThreads(eq(sessionPtr1),
769                 eq(SESSION_TIDS_A));
770         verifyAllHintsEnabled(session1, true);
771     }
772 
773     private void verifyAllHintsEnabled(AppHintSession session, boolean verifyEnabled) {
774         session.reportActualWorkDuration2(new WorkDuration[]{makeWorkDuration(1, 3, 2, 1, 1000)});
775         session.reportActualWorkDuration(new long[]{1}, new long[]{2});
776         session.updateTargetWorkDuration(3);
777         session.setMode(0, true);
778         session.sendHint(1);
779         if (verifyEnabled) {
780             verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(
781                     eq(session.mHalSessionPtr), any());
782             verify(mNativeWrapperMock, times(1)).halSetMode(eq(session.mHalSessionPtr), anyInt(),
783                     anyBoolean());
784             verify(mNativeWrapperMock, times(1)).halUpdateTargetWorkDuration(
785                     eq(session.mHalSessionPtr), anyLong());
786             verify(mNativeWrapperMock, times(1)).halSendHint(eq(session.mHalSessionPtr), anyInt());
787         } else {
788             verify(mNativeWrapperMock, never()).halReportActualWorkDuration(
789                     eq(session.mHalSessionPtr), any());
790             verify(mNativeWrapperMock, never()).halSetMode(eq(session.mHalSessionPtr), anyInt(),
791                     anyBoolean());
792             verify(mNativeWrapperMock, never()).halUpdateTargetWorkDuration(
793                     eq(session.mHalSessionPtr), anyLong());
794             verify(mNativeWrapperMock, never()).halSendHint(eq(session.mHalSessionPtr), anyInt());
795         }
796     }
797 
798     private int[] createThreads(int threadCount, CountDownLatch stopLatch)
799             throws InterruptedException {
800         int[] tids = new int[threadCount];
801         AtomicInteger k = new AtomicInteger(0);
802         CountDownLatch latch = new CountDownLatch(threadCount);
803         for (int j = 0; j < threadCount; j++) {
804             Thread thread = new Thread(() -> {
805                 try {
806                     tids[k.getAndIncrement()] = android.os.Process.myTid();
807                     latch.countDown();
808                     stopLatch.await();
809                 } catch (InterruptedException e) {
810                     throw new RuntimeException(e);
811                 }
812             });
813             thread.start();
814         }
815         latch.await();
816         return tids;
817     }
818 
819     @Test
820     public void testSetMode() throws Exception {
821         HintManagerService service = createService();
822         IBinder token = new Binder();
823         SessionCreationConfig creationConfig =
824                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
825 
826         AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
827                 .createHintSessionWithConfig(token, SessionTag.OTHER,
828                         creationConfig, new SessionConfig());
829 
830         a.setMode(0, true);
831         verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(),
832                 eq(0), eq(true));
833 
834         a.setMode(0, false);
835         verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(),
836                 eq(0), eq(false));
837     }
838 
839     @Test
840     public void testSetModeSessionInBackGround() throws Exception {
841         HintManagerService service = createService();
842         IBinder token = new Binder();
843         SessionCreationConfig creationConfig =
844                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
845 
846         AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
847                 .createHintSessionWithConfig(token, SessionTag.OTHER,
848                         creationConfig, new SessionConfig());
849 
850         // Set session to background, then the duration would not be updated.
851         service.mUidObserver.onUidStateChanged(
852                 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
853         FgThread.getHandler().runWithScissors(() -> {
854         }, 500);
855         assertFalse(service.mUidObserver.isUidForeground(a.mUid));
856         a.setMode(0, true);
857         verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean());
858     }
859 
860     @Test
861     public void testSetModeInvalid() throws Exception {
862         HintManagerService service = createService();
863         IBinder token = new Binder();
864         SessionCreationConfig creationConfig =
865                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
866 
867         AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
868                 .createHintSessionWithConfig(token, SessionTag.OTHER,
869                         creationConfig, new SessionConfig());
870 
871         assertThrows(IllegalArgumentException.class, () -> {
872             a.setMode(-1, true);
873         });
874     }
875 
876     @Test
877     public void testSetModeUponSessionCreation() throws Exception {
878         HintManagerService service = createService();
879         IBinder token = new Binder();
880         SessionCreationConfig creationConfig =
881                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
882         creationConfig.modesToEnable = new int[] {0, 1};
883 
884         AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
885                 .createHintSessionWithConfig(token, SessionTag.OTHER,
886                         creationConfig, new SessionConfig());
887         assertNotNull(a);
888         verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(),
889                 eq(0), eq(true));
890         verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(),
891                 eq(1), eq(true));
892     }
893 
894     @Test
895     public void testGetChannel() throws Exception {
896         HintManagerService service = createService();
897         Binder token = new Binder();
898 
899         // Should only call once, after caching the first call
900         ChannelConfig config = service.getBinderServiceInstance().getSessionChannel(token);
901         ChannelConfig config2 = service.getBinderServiceInstance().getSessionChannel(token);
902         verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
903         assertEquals(config.readFlagBitmask, mConfig.readFlagBitmask);
904         assertEquals(config.writeFlagBitmask, mConfig.writeFlagBitmask);
905         assertEquals(config2.readFlagBitmask, mConfig.readFlagBitmask);
906         assertEquals(config2.writeFlagBitmask, mConfig.writeFlagBitmask);
907     }
908 
909     @Test
910     public void testGetChannelTwice() throws Exception {
911         HintManagerService service = createService();
912         Binder token = new Binder();
913 
914         service.getBinderServiceInstance().getSessionChannel(token);
915         verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
916         service.getBinderServiceInstance().closeSessionChannel();
917         verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
918 
919         clearInvocations(mIPowerMock);
920 
921         service.getBinderServiceInstance().getSessionChannel(token);
922         verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
923         service.getBinderServiceInstance().closeSessionChannel();
924         verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
925     }
926 
927     @Test
928     public void testGetChannelFails() throws Exception {
929         HintManagerService service = createService();
930         Binder token = new Binder();
931 
932         when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenThrow(RemoteException.class);
933 
934         assertThrows(IllegalStateException.class, () -> {
935             service.getBinderServiceInstance().getSessionChannel(token);
936         });
937     }
938 
939 
940     @Test
941     public void testGetChannelBadVersion() throws Exception {
942         when(mIPowerMock.getInterfaceVersion()).thenReturn(3);
943         HintManagerService service = createService();
944         Binder token = new Binder();
945 
946         reset(mIPowerMock);
947         when(mIPowerMock.getInterfaceVersion()).thenReturn(3);
948         when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig);
949 
950         ChannelConfig channel = service.getBinderServiceInstance().getSessionChannel(token);
951         verify(mIPowerMock, times(0)).getSessionChannel(eq(TGID), eq(UID));
952         assertNull(channel);
953     }
954 
955     @Test
956     public void testCloseChannel() throws Exception {
957         HintManagerService service = createService();
958         Binder token = new Binder();
959 
960         service.getBinderServiceInstance().getSessionChannel(token);
961         service.getBinderServiceInstance().closeSessionChannel();
962         verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
963     }
964 
965     @Test
966     public void testCloseChannelFails() throws Exception {
967         HintManagerService service = createService();
968         Binder token = new Binder();
969 
970         service.getBinderServiceInstance().getSessionChannel(token);
971 
972         doThrow(RemoteException.class).when(mIPowerMock).closeSessionChannel(anyInt(), anyInt());
973 
974         assertThrows(IllegalStateException.class, () -> {
975             service.getBinderServiceInstance().closeSessionChannel();
976         });
977     }
978 
979     @Test
980     public void testDoubleClose() throws Exception {
981         HintManagerService service = createService();
982         Binder token = new Binder();
983 
984         service.getBinderServiceInstance().getSessionChannel(token);
985         service.getBinderServiceInstance().closeSessionChannel();
986         service.getBinderServiceInstance().closeSessionChannel();
987         verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
988     }
989 
990     // This test checks that concurrent operations from different threads on IHintService,
991     // IHintSession and UidObserver will not cause data race or deadlock. Ideally we should also
992     // check the output of threads' reportActualDuration performance to detect lock starvation
993     // but the result is not stable, so it's better checked manually.
994     @Test
995     public void testConcurrency() throws Exception {
996         HintManagerService service = createServiceWithFakeWrapper();
997         // initialize session threads to run in parallel
998         final int sessionCount = 10;
999         // the signal that the main thread will send to session threads to check for run or exit
1000         AtomicReference<Boolean> shouldRun = new AtomicReference<>(true);
1001         // the signal for main test thread to wait for session threads and notifier thread to
1002         // finish and exit
1003         CountDownLatch latch = new CountDownLatch(sessionCount + 1);
1004         // list of exceptions with one per session thread or notifier thread
1005         List<AtomicReference<Exception>> execs = new ArrayList<>(sessionCount + 1);
1006         List<Thread> threads = new ArrayList<>(sessionCount + 1);
1007         for (int i = 0; i < sessionCount; i++) {
1008             final AtomicReference<Exception> exec = new AtomicReference<>();
1009             execs.add(exec);
1010             int j = i;
1011             Thread app = new Thread(() -> {
1012                 try {
1013                     while (shouldRun.get()) {
1014                         runAppHintSession(service, j, shouldRun);
1015                     }
1016                 } catch (Exception e) {
1017                     exec.set(e);
1018                 } finally {
1019                     latch.countDown();
1020                 }
1021             });
1022             threads.add(app);
1023         }
1024 
1025         // initialize a UID state notifier thread to run in parallel
1026         final AtomicReference<Exception> notifierExec = new AtomicReference<>();
1027         execs.add(notifierExec);
1028         Thread notifier = new Thread(() -> {
1029             try {
1030                 long min = Long.MAX_VALUE;
1031                 long max = Long.MIN_VALUE;
1032                 long sum = 0;
1033                 int count = 0;
1034                 while (shouldRun.get()) {
1035                     long start = System.nanoTime();
1036                     service.mUidObserver.onUidStateChanged(UID,
1037                             ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0);
1038                     long elapsed = System.nanoTime() - start;
1039                     sum += elapsed;
1040                     count++;
1041                     min = Math.min(min, elapsed);
1042                     max = Math.max(max, elapsed);
1043                     LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
1044                     service.mUidObserver.onUidStateChanged(UID,
1045                             ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
1046                     // let the cleanup work proceed
1047                     LockSupport.parkNanos(
1048                             TimeUnit.MILLISECONDS.toNanos(500) + TimeUnit.MILLISECONDS.toNanos(
1049                                     CLEAN_UP_UID_DELAY_MILLIS));
1050                 }
1051                 Log.d(TAG, "notifier thread min " + min + " max " + max + " avg " + sum / count);
1052                 service.mUidObserver.onUidGone(UID, true);
1053             } catch (Exception e) {
1054                 notifierExec.set(e);
1055             } finally {
1056                 latch.countDown();
1057             }
1058         });
1059         threads.add(notifier);
1060 
1061         // start all the threads
1062         for (Thread thread : threads) {
1063             thread.start();
1064         }
1065         // keep the test running for a few seconds
1066         LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(CONCURRENCY_TEST_DURATION_SEC));
1067         // send signal to stop all threads
1068         shouldRun.set(false);
1069         // wait for all threads to exit
1070         latch.await();
1071         // check if any thread throws exception
1072         for (AtomicReference<Exception> exec : execs) {
1073             if (exec.get() != null) {
1074                 throw exec.get();
1075             }
1076         }
1077     }
1078 
1079     private void runAppHintSession(HintManagerService service, int logId,
1080             AtomicReference<Boolean> shouldRun) throws Exception {
1081         IBinder token = new Binder();
1082         SessionCreationConfig creationConfig =
1083                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
1084 
1085         AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
1086                 .createHintSessionWithConfig(token, SessionTag.OTHER,
1087                         creationConfig, new SessionConfig());
1088         // we will start some threads and get their valid TIDs to update
1089         int threadCount = 3;
1090         // the list of TIDs
1091         int[] tids = new int[threadCount];
1092         // atomic index for each thread to set its TID in the list
1093         AtomicInteger k = new AtomicInteger(0);
1094         // signal for the session main thread to wait for child threads to finish updating TIDs
1095         CountDownLatch latch = new CountDownLatch(threadCount);
1096         // signal for the session main thread to notify child threads to exit
1097         CountDownLatch stopLatch = new CountDownLatch(1);
1098         for (int j = 0; j < threadCount; j++) {
1099             Thread thread = new Thread(() -> {
1100                 try {
1101                     tids[k.getAndIncrement()] = android.os.Process.myTid();
1102                     latch.countDown();
1103                     stopLatch.await();
1104                 } catch (InterruptedException e) {
1105                     throw new RuntimeException(e);
1106                 }
1107             });
1108             thread.start();
1109         }
1110         latch.await();
1111         a.setThreads(tids);
1112         // we don't need the threads to exist after update
1113         stopLatch.countDown();
1114         a.updateTargetWorkDuration(5);
1115         // measure the time it takes in HintManagerService to run reportActualDuration
1116         long min = Long.MAX_VALUE;
1117         long max = Long.MIN_VALUE;
1118         long sum = 0;
1119         int count = 0;
1120         List<Long> values = new ArrayList<>();
1121         long testStart = System.nanoTime();
1122         // run report actual for 4-second per cycle
1123         while (shouldRun.get() && System.nanoTime() - testStart < TimeUnit.SECONDS.toNanos(
1124                 Math.min(4, CONCURRENCY_TEST_DURATION_SEC))) {
1125             long start = System.nanoTime();
1126             a.reportActualWorkDuration(new long[]{5}, new long[]{start});
1127             long elapsed = System.nanoTime() - start;
1128             values.add(elapsed);
1129             LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(5));
1130             sum += elapsed;
1131             count++;
1132             min = Math.min(min, elapsed);
1133             max = Math.max(max, elapsed);
1134         }
1135         Collections.sort(values);
1136         if (!values.isEmpty()) {
1137             Log.d(TAG, "app thread " + logId + " min " + min + " max " + max
1138                     + " avg " + sum / count + " count " + count
1139                     + " 80th " + values.get((int) (values.size() * 0.8))
1140                     + " 90th " + values.get((int) (values.size() * 0.9))
1141                     + " 95th " + values.get((int) (values.size() * 0.95)));
1142         } else {
1143             Log.w(TAG, "No reportActualWorkDuration executed");
1144         }
1145         a.close();
1146     }
1147 
1148     @Test
1149     public void testReportActualWorkDuration2() throws Exception {
1150         HintManagerService service = createService();
1151         IBinder token = new Binder();
1152         SessionCreationConfig creationConfig =
1153                 makeSessionCreationConfig(SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
1154 
1155         AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
1156                 .createHintSessionWithConfig(token, SessionTag.OTHER,
1157                         creationConfig, new SessionConfig());
1158 
1159         a.updateTargetWorkDuration(100L);
1160         a.reportActualWorkDuration2(WORK_DURATIONS_FIVE);
1161         verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(),
1162                 eq(WORK_DURATIONS_FIVE));
1163 
1164         assertThrows(IllegalArgumentException.class, () -> {
1165             a.reportActualWorkDuration2(new WorkDuration[] {});
1166         });
1167 
1168         assertThrows(IllegalArgumentException.class, () -> {
1169             a.reportActualWorkDuration2(
1170                     new WorkDuration[] {makeWorkDuration(1L, 11L, -1L, 8L, 4L)});
1171         });
1172 
1173         assertThrows(IllegalArgumentException.class, () -> {
1174             a.reportActualWorkDuration2(new WorkDuration[] {makeWorkDuration(1L, 0L, 1L, 8L, 4L)});
1175         });
1176 
1177         assertThrows(IllegalArgumentException.class, () -> {
1178             a.reportActualWorkDuration2(new WorkDuration[] {makeWorkDuration(1L, 11L, 1L, 0L, 0L)});
1179         });
1180 
1181         assertThrows(IllegalArgumentException.class, () -> {
1182             a.reportActualWorkDuration2(
1183                     new WorkDuration[] {makeWorkDuration(1L, 11L, 1L, 8L, -1L)});
1184         });
1185 
1186         reset(mNativeWrapperMock);
1187         // Set session to background, then the duration would not be updated.
1188         service.mUidObserver.onUidStateChanged(
1189                 a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
1190 
1191         // Using CountDownLatch to ensure above onUidStateChanged() job was digested.
1192         final CountDownLatch latch = new CountDownLatch(1);
1193         FgThread.getHandler().post(() -> {
1194             latch.countDown();
1195         });
1196         latch.await();
1197 
1198         assertFalse(service.mUidObserver.isUidForeground(a.mUid));
1199         a.reportActualWorkDuration2(WORK_DURATIONS_FIVE);
1200         verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
1201     }
1202 
1203     @Test
1204     public void testChannelDiesWhenTokenDies() throws Exception {
1205         HintManagerService service = createService();
1206 
1207         class DyingToken extends Binder {
1208             DeathRecipient mToNotify;
1209             @Override
1210             public void linkToDeath(@NonNull DeathRecipient recipient, int flags) {
1211                 mToNotify = recipient;
1212                 super.linkToDeath(recipient, flags);
1213             }
1214 
1215             public void fakeDeath() {
1216                 mToNotify.binderDied();
1217             }
1218         }
1219 
1220         DyingToken token = new DyingToken();
1221 
1222         service.getBinderServiceInstance().getSessionChannel(token);
1223         verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
1224         assertTrue(service.hasChannel(TGID, UID));
1225 
1226         token.fakeDeath();
1227 
1228         assertFalse(service.hasChannel(TGID, UID));
1229         verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
1230 
1231         clearInvocations(mIPowerMock);
1232 
1233         token = new DyingToken();
1234         service.getBinderServiceInstance().getSessionChannel(token);
1235         verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
1236         assertTrue(service.hasChannel(TGID, UID));
1237     }
1238 
1239     @Test
1240     public void testHeadroomPowerHalNotSupported() throws Exception {
1241         when(mIPowerMock.getInterfaceVersion()).thenReturn(5);
1242         HintManagerService service = createService();
1243         assertThrows(UnsupportedOperationException.class, () -> {
1244             service.getBinderServiceInstance().getCpuHeadroom(null);
1245         });
1246         assertThrows(UnsupportedOperationException.class, () -> {
1247             service.getBinderServiceInstance().getGpuHeadroom(null);
1248         });
1249         assertThrows(UnsupportedOperationException.class, () -> {
1250             service.getBinderServiceInstance().getCpuHeadroomMinIntervalMillis();
1251         });
1252         assertThrows(UnsupportedOperationException.class, () -> {
1253             service.getBinderServiceInstance().getGpuHeadroomMinIntervalMillis();
1254         });
1255     }
1256 
1257     @Test
1258     public void testCpuHeadroomCache() throws Exception {
1259         CpuHeadroomParamsInternal params1 = new CpuHeadroomParamsInternal();
1260         CpuHeadroomParams halParams1 = new CpuHeadroomParams();
1261         halParams1.calculationType = CpuHeadroomParams.CalculationType.MIN;
1262         halParams1.tids = new int[]{Process.myPid()};
1263 
1264         CpuHeadroomParamsInternal params2 = new CpuHeadroomParamsInternal();
1265         params2.usesDeviceHeadroom = true;
1266         params2.calculationType = CpuHeadroomParams.CalculationType.MIN;
1267         CpuHeadroomParams halParams2 = new CpuHeadroomParams();
1268         halParams2.calculationType = CpuHeadroomParams.CalculationType.MIN;
1269         halParams2.tids = new int[]{};
1270 
1271         CpuHeadroomParamsInternal params3 = new CpuHeadroomParamsInternal();
1272         params3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
1273         CpuHeadroomParams halParams3 = new CpuHeadroomParams();
1274         halParams3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
1275         halParams3.tids = new int[]{Process.myPid()};
1276 
1277         // this params should not be cached as the window is not default
1278         CpuHeadroomParamsInternal params4 = new CpuHeadroomParamsInternal();
1279         params4.calculationWindowMillis = 123;
1280         CpuHeadroomParams halParams4 = new CpuHeadroomParams();
1281         halParams4.calculationType = CpuHeadroomParams.CalculationType.MIN;
1282         halParams4.calculationWindowMillis = 123;
1283         halParams4.tids = new int[]{Process.myPid()};
1284 
1285         float headroom1 = 0.1f;
1286         CpuHeadroomResult halRet1 = CpuHeadroomResult.globalHeadroom(headroom1);
1287         when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(halRet1);
1288         float headroom2 = 0.2f;
1289         CpuHeadroomResult halRet2 = CpuHeadroomResult.globalHeadroom(headroom2);
1290         when(mIPowerMock.getCpuHeadroom(eq(halParams2))).thenReturn(halRet2);
1291         float headroom3 = 0.3f;
1292         CpuHeadroomResult halRet3 = CpuHeadroomResult.globalHeadroom(headroom3);
1293         when(mIPowerMock.getCpuHeadroom(eq(halParams3))).thenReturn(halRet3);
1294         float headroom4 = 0.4f;
1295         CpuHeadroomResult halRet4 = CpuHeadroomResult.globalHeadroom(headroom4);
1296         when(mIPowerMock.getCpuHeadroom(eq(halParams4))).thenReturn(halRet4);
1297 
1298         HintManagerService service = createService();
1299         clearInvocations(mIPowerMock);
1300 
1301         assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
1302         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
1303         assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
1304         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2));
1305         assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
1306         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3));
1307         assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
1308         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
1309 
1310         // verify cache is working
1311         clearInvocations(mIPowerMock);
1312         assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
1313         assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
1314         assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
1315         assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
1316         verify(mIPowerMock, times(1)).getCpuHeadroom(any());
1317         verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));
1318         verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams2));
1319         verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
1320         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
1321 
1322         // after 1 more second it should be served with cache still
1323         Thread.sleep(1000);
1324         clearInvocations(mIPowerMock);
1325         assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
1326         assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
1327         assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
1328         assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
1329         verify(mIPowerMock, times(1)).getCpuHeadroom(any());
1330         verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));
1331         verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams2));
1332         verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
1333         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
1334 
1335         // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
1336         Thread.sleep(1100);
1337         clearInvocations(mIPowerMock);
1338         assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
1339         assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
1340         assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
1341         assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
1342         verify(mIPowerMock, times(4)).getCpuHeadroom(any());
1343         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
1344         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2));
1345         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3));
1346         verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
1347     }
1348 
1349     @Test
1350     public void testGpuHeadroomCache() throws Exception {
1351         GpuHeadroomParamsInternal params1 = new GpuHeadroomParamsInternal();
1352         GpuHeadroomParams halParams1 = new GpuHeadroomParams();
1353         halParams1.calculationType = GpuHeadroomParams.CalculationType.MIN;
1354 
1355         GpuHeadroomParamsInternal params2 = new GpuHeadroomParamsInternal();
1356         params2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
1357         params2.calculationWindowMillis = 123;
1358         GpuHeadroomParams halParams2 = new GpuHeadroomParams();
1359         halParams2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
1360         halParams2.calculationWindowMillis = 123;
1361 
1362         float headroom1 = 0.1f;
1363         GpuHeadroomResult halRet1 = GpuHeadroomResult.globalHeadroom(headroom1);
1364         when(mIPowerMock.getGpuHeadroom(eq(halParams1))).thenReturn(halRet1);
1365         float headroom2 = 0.2f;
1366         GpuHeadroomResult halRet2 = GpuHeadroomResult.globalHeadroom(headroom2);
1367         when(mIPowerMock.getGpuHeadroom(eq(halParams2))).thenReturn(halRet2);
1368         HintManagerService service = createService();
1369         clearInvocations(mIPowerMock);
1370 
1371         assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
1372         assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
1373         verify(mIPowerMock, times(2)).getGpuHeadroom(any());
1374         verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1));
1375         verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
1376 
1377         // verify cache is working
1378         clearInvocations(mIPowerMock);
1379         assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
1380         assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
1381         verify(mIPowerMock, times(1)).getGpuHeadroom(any());
1382         verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
1383         verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
1384 
1385         // after 1 more second it should be served with cache still
1386         Thread.sleep(1000);
1387         clearInvocations(mIPowerMock);
1388         assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
1389         assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
1390         verify(mIPowerMock, times(1)).getGpuHeadroom(any());
1391         verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
1392         verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
1393 
1394         // after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
1395         Thread.sleep(1100);
1396         clearInvocations(mIPowerMock);
1397         assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
1398         assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
1399         verify(mIPowerMock, times(2)).getGpuHeadroom(any());
1400         verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1));
1401         verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
1402     }
1403 }
1404