summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Wei Wang <wvw@google.com> 2021-04-11 00:16:23 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2021-04-11 00:16:23 +0000
commit080abd80f4f648f2e45fb4eb1e409bca1b128d4f (patch)
tree98cea2c2e704763cdcc44f61e4f94960382d275b
parent84f96c845ba75c43d7ad4679065086b79d773175 (diff)
parent027b2188473bd48c34cdafad1705abac7fff9356 (diff)
Merge changes from topic "android_dynamic_performance_framework" into sc-dev
* changes: Integrate HWUI with PerformanceHintManager ADPF: Add HintManagerService ADPF: PerformanceHintManager API implementation
-rw-r--r--core/api/current.txt12
-rw-r--r--core/java/android/app/SystemServiceRegistry.java13
-rw-r--r--core/java/android/content/Context.java8
-rw-r--r--core/java/android/os/IHintManager.aidl33
-rw-r--r--core/java/android/os/IHintSession.aidl25
-rw-r--r--core/java/android/os/PerformanceHintManager.java226
-rw-r--r--core/java/android/view/Choreographer.java2
-rw-r--r--core/java/android/view/FrameMetrics.java23
-rw-r--r--core/tests/coretests/src/android/os/PerformanceHintManagerTest.java211
-rw-r--r--graphics/java/android/graphics/FrameInfo.java8
-rw-r--r--graphics/java/android/graphics/HardwareRenderer.java68
-rw-r--r--libs/hwui/FrameInfo.cpp29
-rw-r--r--libs/hwui/FrameInfo.h3
-rw-r--r--libs/hwui/Properties.cpp7
-rw-r--r--libs/hwui/Properties.h17
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRenderer.cpp95
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.cpp29
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.h6
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp6
-rw-r--r--libs/hwui/renderthread/RenderProxy.h2
-rw-r--r--libs/hwui/thread/CommonPool.cpp26
-rw-r--r--libs/hwui/thread/CommonPool.h5
-rw-r--r--services/core/java/com/android/server/power/hint/HintManagerService.java449
-rw-r--r--services/core/jni/Android.bp7
-rw-r--r--services/core/jni/com_android_server_hint_HintManagerService.cpp166
-rw-r--r--services/core/jni/com_android_server_power_PowerManagerService.cpp2
-rw-r--r--services/core/jni/onload.cpp2
-rw-r--r--services/java/com/android/server/SystemServer.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java268
29 files changed, 1701 insertions, 52 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 1f7da095a1a7..1ac6d946f054 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10644,6 +10644,7 @@ package android.content {
field public static final String NOTIFICATION_SERVICE = "notification";
field public static final String NSD_SERVICE = "servicediscovery";
field public static final String PEOPLE_SERVICE = "people";
+ field public static final String PERFORMANCE_HINT_SERVICE = "performance_hint";
field public static final String POWER_SERVICE = "power";
field public static final String PRINT_SERVICE = "print";
field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1
@@ -31559,6 +31560,17 @@ package android.os {
field public static final int PATTERN_SUFFIX = 4; // 0x4
}
+ public final class PerformanceHintManager {
+ method @Nullable public android.os.PerformanceHintManager.Session createHintSession(@NonNull int[], long);
+ method public long getPreferredUpdateRateNanos();
+ }
+
+ public static class PerformanceHintManager.Session implements java.io.Closeable {
+ method public void close();
+ method public void reportActualWorkDuration(long);
+ method public void updateTargetWorkDuration(long);
+ }
+
public final class PersistableBundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
ctor public PersistableBundle();
ctor public PersistableBundle(int);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 47a9fbb5fe4a..91dad2a1f13c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -158,12 +158,14 @@ import android.os.IBatteryPropertiesRegistrar;
import android.os.IBinder;
import android.os.IDumpstate;
import android.os.IHardwarePropertiesManager;
+import android.os.IHintManager;
import android.os.IPowerManager;
import android.os.IRecoverySystem;
import android.os.ISystemUpdateManager;
import android.os.IThermalService;
import android.os.IUserManager;
import android.os.IncidentManager;
+import android.os.PerformanceHintManager;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.ServiceManager;
@@ -592,6 +594,17 @@ public final class SystemServiceRegistry {
ctx.mMainThread.getHandler());
}});
+ registerService(Context.PERFORMANCE_HINT_SERVICE, PerformanceHintManager.class,
+ new CachedServiceFetcher<PerformanceHintManager>() {
+ @Override
+ public PerformanceHintManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder hintBinder = ServiceManager.getServiceOrThrow(
+ Context.PERFORMANCE_HINT_SERVICE);
+ IHintManager hintService = IHintManager.Stub.asInterface(hintBinder);
+ return new PerformanceHintManager(hintService);
+ }});
+
registerService(Context.RECOVERY_SERVICE, RecoverySystem.class,
new CachedServiceFetcher<RecoverySystem>() {
@Override
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 1226858bba71..5d66cdf880b8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5226,6 +5226,14 @@ public abstract class Context {
/**
* Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.os.PerformanceHintManager} for accessing the performance hinting service.
+ *
+ * @see #getSystemService(String)
+ */
+ public static final String PERFORMANCE_HINT_SERVICE = "performance_hint";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link android.content.pm.ShortcutManager} for accessing the launcher shortcut service.
*
* @see #getSystemService(String)
diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl
new file mode 100644
index 000000000000..661b95affaf3
--- /dev/null
+++ b/core/java/android/os/IHintManager.aidl
@@ -0,0 +1,33 @@
+/*
+ *
+ * Copyright 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 android.os;
+
+import android.os.IHintSession;
+
+/** {@hide} */
+interface IHintManager {
+ /**
+ * Creates a {@link Session} for the given set of threads and associates to a binder token.
+ */
+ IHintSession createHintSession(in IBinder token, in int[] tids, long durationNanos);
+
+ /**
+ * Get preferred rate limit in nano second.
+ */
+ long getHintSessionPreferredRate();
+}
diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl
new file mode 100644
index 000000000000..09bc4cc4eb7e
--- /dev/null
+++ b/core/java/android/os/IHintSession.aidl
@@ -0,0 +1,25 @@
+/*
+ *
+ * Copyright 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 android.os;
+
+/** {@hide} */
+oneway interface IHintSession {
+ void updateTargetWorkDuration(long targetDurationNanos);
+ void reportActualWorkDuration(in long[] actualDurationNanos, in long[] timeStampNanos);
+ void close();
+}
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
new file mode 100644
index 000000000000..6791844a2a00
--- /dev/null
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -0,0 +1,226 @@
+/*
+ * 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 android.os;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.Closeable;
+import java.util.ArrayList;
+
+/** The PerformanceHintManager allows apps to send performance hint to system. */
+@SystemService(Context.PERFORMANCE_HINT_SERVICE)
+public final class PerformanceHintManager {
+ private static final String TAG = "PerformanceHintManager";
+ private final IHintManager mService;
+ // HAL preferred update rate
+ private final long mPreferredRate;
+
+ /** @hide */
+ public PerformanceHintManager(IHintManager service) {
+ mService = service;
+ try {
+ mPreferredRate = mService.getHintSessionPreferredRate();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a {@link Session} for the given set of threads and sets their initial target work
+ * duration.
+ *
+ * @param tids The list of threads to be associated with this session. They must be part of
+ * this process' thread group.
+ * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new
+ * session.
+ * @return the new session if it is supported on this device, null if hint session is not
+ * supported on this device.
+ */
+ @Nullable
+ public Session createHintSession(@NonNull int[] tids, long initialTargetWorkDurationNanos) {
+ try {
+ IBinder token = new Binder();
+ IHintSession session = mService.createHintSession(token, tids,
+ initialTargetWorkDurationNanos);
+ if (session == null) return null;
+ return new Session(session, sNanoClock, mPreferredRate,
+ initialTargetWorkDurationNanos);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get preferred update rate information for this device.
+ *
+ * @return the preferred update rate supported by device software.
+ */
+ public long getPreferredUpdateRateNanos() {
+ return mPreferredRate;
+ }
+
+ /**
+ * A Session represents a group of threads with an inter-related workload such that hints for
+ * their performance should be considered as a unit. The threads in a given session should be
+ * long-life and not created or destroyed dynamically.
+ *
+ * <p>Each session is expected to have a periodic workload with a target duration for each
+ * cycle. The cycle duration is likely greater than the target work duration to allow other
+ * parts of the pipeline to run within the available budget. For example, a renderer thread may
+ * work at 60hz in order to produce frames at the display's frame but have a target work
+ * duration of only 6ms.</p>
+ *
+ * <p>Any call in this class will change its internal data, so you must do your own thread
+ * safety to protect from racing.</p>
+ *
+ * <p>Note that the target work duration can be {@link #updateTargetWorkDuration(long) updated}
+ * if workloads change.</p>
+ *
+ * <p>After each cycle of work, the client is expected to
+ * {@link #reportActualWorkDuration(long) report} the actual time taken to complete.</p>
+ *
+ * <p>All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.</p>
+ */
+ public static class Session implements Closeable {
+ private final IHintSession mSession;
+ private final NanoClock mElapsedRealtimeClock;
+ // Target duration for choosing update rate
+ private long mTargetDurationInNanos;
+ // HAL preferred update rate
+ private long mPreferredRate;
+ // Last update timestamp
+ private long mLastUpdateTimeStamp = -1L;
+ // Cached samples
+ private final ArrayList<Long> mActualDurationNanos;
+ private final ArrayList<Long> mTimeStampNanos;
+
+ /** @hide */
+ public Session(IHintSession session, NanoClock elapsedRealtimeClock, long preferredRate,
+ long durationNanos) {
+ mSession = session;
+ mElapsedRealtimeClock = elapsedRealtimeClock;
+ mTargetDurationInNanos = durationNanos;
+ mPreferredRate = preferredRate;
+ mActualDurationNanos = new ArrayList<Long>();
+ mTimeStampNanos = new ArrayList<Long>();
+ mLastUpdateTimeStamp = mElapsedRealtimeClock.nanos();
+ }
+
+ /**
+ * Updates this session's target duration for each cycle of work.
+ *
+ * @param targetDurationNanos the new desired duration in nanoseconds
+ */
+ public void updateTargetWorkDuration(long targetDurationNanos) {
+ Preconditions.checkArgumentPositive(targetDurationNanos, "the hint target duration"
+ + " should be positive.");
+ try {
+ mSession.updateTargetWorkDuration(targetDurationNanos);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mTargetDurationInNanos = targetDurationNanos;
+ /**
+ * Most of the workload is target_duration dependent, so now clear the cached samples
+ * as they are most likely obsolete.
+ */
+ mActualDurationNanos.clear();
+ mTimeStampNanos.clear();
+ mLastUpdateTimeStamp = mElapsedRealtimeClock.nanos();
+ }
+
+ /**
+ * Reports the actual duration for the last cycle of work.
+ *
+ * <p>The system will attempt to adjust the core placement of the threads within the thread
+ * group and/or the frequency of the core on which they are run to bring the actual duration
+ * close to the target duration.</p>
+ *
+ * @param actualDurationNanos how long the thread group took to complete its last task in
+ * nanoseconds
+ */
+ public void reportActualWorkDuration(long actualDurationNanos) {
+ Preconditions.checkArgumentPositive(actualDurationNanos, "the actual duration should"
+ + " be positive.");
+ final long now = mElapsedRealtimeClock.nanos();
+ mActualDurationNanos.add(actualDurationNanos);
+ mTimeStampNanos.add(now);
+
+ /**
+ * Use current sample to determine the rate limit. We can pick a shorter rate limit
+ * if any sample underperformed, however, it could be the lower level system is slow
+ * to react. So here we explicitly choose the rate limit with the latest sample.
+ */
+ long rateLimit =
+ actualDurationNanos > mTargetDurationInNanos ? mPreferredRate
+ : 10 * mPreferredRate;
+
+ if (now - mLastUpdateTimeStamp <= rateLimit) {
+ return;
+ }
+ Preconditions.checkState(mActualDurationNanos.size() == mTimeStampNanos.size());
+ final int size = mActualDurationNanos.size();
+ long[] actualDurationArray = new long[size];
+ long[] timeStampArray = new long[size];
+ for (int i = 0; i < size; i++) {
+ actualDurationArray[i] = mActualDurationNanos.get(i);
+ timeStampArray[i] = mTimeStampNanos.get(i);
+ }
+ try {
+ mSession.reportActualWorkDuration(actualDurationArray, timeStampArray);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mActualDurationNanos.clear();
+ mTimeStampNanos.clear();
+ mLastUpdateTimeStamp = now;
+ }
+
+ /**
+ * Ends the current hint session.
+ *
+ * <p>Once called, you should not call anything else on this object.</p>
+ */
+ public void close() {
+ try {
+ mSession.close();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * The interface is to make the FakeClock for testing.
+ * @hide
+ */
+ public interface NanoClock {
+ /** Gets the current nanosecond instant of the clock. */
+ long nanos();
+ }
+
+ private static final NanoClock sNanoClock = new NanoClock() {
+ public long nanos() {
+ return SystemClock.elapsedRealtimeNanos();
+ }
+ };
+}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 5c65c659e576..7b3a8a64fc37 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -737,7 +737,7 @@ public final class Choreographer {
}
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
- vsyncEventData.frameDeadline);
+ vsyncEventData.frameDeadline, startNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
mLastVsyncEventData = vsyncEventData;
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index eb49e52d5050..9cdf91a55c39 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -242,18 +242,19 @@ public final class FrameMetrics {
int PERFORM_TRAVERSALS_START = 7;
int DRAW_START = 8;
int FRAME_DEADLINE = 9;
- int SYNC_QUEUED = 10;
- int SYNC_START = 11;
- int ISSUE_DRAW_COMMANDS_START = 12;
- int SWAP_BUFFERS = 13;
- int FRAME_COMPLETED = 14;
- int DEQUEUE_BUFFER_DURATION = 15;
- int QUEUE_BUFFER_DURATION = 16;
- int GPU_COMPLETED = 17;
- int SWAP_BUFFERS_COMPLETED = 18;
- int DISPLAY_PRESENT_TIME = 19;
+ int FRAME_START_TIME = 10;
+ int SYNC_QUEUED = 11;
+ int SYNC_START = 12;
+ int ISSUE_DRAW_COMMANDS_START = 13;
+ int SWAP_BUFFERS = 14;
+ int FRAME_COMPLETED = 15;
+ int DEQUEUE_BUFFER_DURATION = 16;
+ int QUEUE_BUFFER_DURATION = 17;
+ int GPU_COMPLETED = 18;
+ int SWAP_BUFFERS_COMPLETED = 19;
+ int DISPLAY_PRESENT_TIME = 20;
- int FRAME_STATS_COUNT = 20; // must always be last and in sync with
+ int FRAME_STATS_COUNT = 21; // must always be last and in sync with
// FrameInfoIndex::NumIndexes in libs/hwui/FrameInfo.h
}
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
new file mode 100644
index 000000000000..7dea82d7ee54
--- /dev/null
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -0,0 +1,211 @@
+/*
+ * 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 android.os;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNotNull;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+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 android.os.PerformanceHintManager.Session;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class PerformanceHintManagerTest {
+ private static final long RATE_1000 = 1000L;
+ private static final long TARGET_166 = 166L;
+ private static final long DEFAULT_TARGET_NS = 16666666L;
+ private PerformanceHintManager mPerformanceHintManager;
+
+ @Mock
+ private IHintSession mIHintSessionMock;
+
+ @Before
+ public void setUp() {
+ mPerformanceHintManager =
+ InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
+ PerformanceHintManager.class);
+ MockitoAnnotations.initMocks(this);
+ }
+
+ private Session createSession() {
+ return mPerformanceHintManager.createHintSession(
+ new int[]{Process.myPid()}, DEFAULT_TARGET_NS);
+ }
+
+ @Test
+ public void testCreateHintSession() {
+ Session a = createSession();
+ Session b = createSession();
+ if (a == null) {
+ assertNull(b);
+ } else {
+ assertNotEquals(a, b);
+ }
+ }
+
+ @Test
+ public void testGetPreferredUpdateRateNanos() {
+ if (createSession() != null) {
+ assertTrue(mPerformanceHintManager.getPreferredUpdateRateNanos() > 0);
+ } else {
+ assertEquals(-1, mPerformanceHintManager.getPreferredUpdateRateNanos());
+ }
+ }
+
+ @Test
+ public void testUpdateTargetWorkDuration() {
+ Session s = createSession();
+ assumeNotNull(s);
+ s.updateTargetWorkDuration(100);
+ }
+
+ @Test
+ public void testUpdateTargetWorkDurationWithNegativeDuration() {
+ Session s = createSession();
+ assumeNotNull(s);
+ assertThrows(IllegalArgumentException.class, () -> {
+ s.updateTargetWorkDuration(-1);
+ });
+ }
+
+ @Test
+ public void testReportActualWorkDuration() {
+ Session s = createSession();
+ assumeNotNull(s);
+ s.updateTargetWorkDuration(100);
+ s.reportActualWorkDuration(1);
+ s.reportActualWorkDuration(100);
+ s.reportActualWorkDuration(1000);
+ }
+
+ @Test
+ public void testReportActualWorkDurationWithIllegalArgument() {
+ Session s = createSession();
+ assumeNotNull(s);
+ s.updateTargetWorkDuration(100);
+ assertThrows(IllegalArgumentException.class, () -> {
+ s.reportActualWorkDuration(-1);
+ });
+ }
+
+ @Test
+ public void testRateLimitWithDurationFastEnough() throws Exception {
+ FakeClock fakeClock = new FakeClock();
+ Session s = new Session(mIHintSessionMock, fakeClock, RATE_1000, TARGET_166);
+
+ reset(mIHintSessionMock);
+ fakeClock.setNow(0);
+ s.updateTargetWorkDuration(TARGET_166);
+
+ s.reportActualWorkDuration(TARGET_166 - 1);
+ s.reportActualWorkDuration(TARGET_166);
+ // we should not see update as the rate should be 10X for over-perform case.
+ verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
+ fakeClock.incrementClock(10 * RATE_1000);
+ s.reportActualWorkDuration(TARGET_166);
+ verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
+ fakeClock.incrementClock(1);
+ s.reportActualWorkDuration(TARGET_166);
+ // we should see update after rate limit
+ verify(mIHintSessionMock, times(1)).reportActualWorkDuration(
+ eq(new long[] {TARGET_166 - 1, TARGET_166, TARGET_166, TARGET_166}),
+ eq(new long[] {0, 0, 10 * RATE_1000, 10 * RATE_1000 + 1}));
+
+ reset(mIHintSessionMock);
+ s.reportActualWorkDuration(TARGET_166);
+ s.reportActualWorkDuration(TARGET_166 - 1);
+ s.reportActualWorkDuration(TARGET_166 - 2);
+ // we should not see update as the rate should be 10X for over-perform case.
+ verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
+ fakeClock.incrementClock(10 * RATE_1000 + 1);
+ s.reportActualWorkDuration(TARGET_166);
+ s.reportActualWorkDuration(TARGET_166 - 1);
+ // we should see update now
+ verify(mIHintSessionMock, times(1)).reportActualWorkDuration(
+ eq(new long[] {TARGET_166, TARGET_166 - 1, TARGET_166 - 2, TARGET_166}),
+ eq(new long[] {10 * RATE_1000 + 1, 10 * RATE_1000 + 1, 10 * RATE_1000 + 1,
+ (10 * RATE_1000 + 1) * 2}));
+ }
+
+ @Test
+ public void testRateLimitWithDurationTooSlow() throws Exception {
+ FakeClock fakeClock = new FakeClock();
+ Session s = new Session(mIHintSessionMock, fakeClock, RATE_1000, TARGET_166);
+
+ reset(mIHintSessionMock);
+ fakeClock.setNow(0);
+ s.updateTargetWorkDuration(TARGET_166);
+
+ verify(mIHintSessionMock, times(1)).updateTargetWorkDuration(eq(TARGET_166));
+ // shouldn't update before rate limit
+ s.reportActualWorkDuration(TARGET_166 + 1);
+ verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
+
+ // shouldn't update when the time is exactly at rate limit
+ fakeClock.incrementClock(RATE_1000);
+ s.reportActualWorkDuration(TARGET_166 + 1);
+ verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
+
+ // should be ready for sending hint
+ fakeClock.incrementClock(1);
+ s.reportActualWorkDuration(TARGET_166 + 1);
+ verify(mIHintSessionMock, times(1)).reportActualWorkDuration(
+ eq(new long[] {TARGET_166 + 1, TARGET_166 + 1, TARGET_166 + 1}),
+ eq(new long[] {0 , RATE_1000, RATE_1000 + 1}));
+ }
+
+ @Test
+ public void testCloseHintSession() {
+ Session s = createSession();
+ assumeNotNull(s);
+ s.close();
+ }
+
+ private static class FakeClock implements PerformanceHintManager.NanoClock {
+ private long mCurrentTime = 0L;
+
+ @Override
+ public long nanos() {
+ return mCurrentTime;
+ }
+
+ public void setNow(long nanos) {
+ mCurrentTime = nanos;
+ }
+
+ public void incrementClock(long nanos) {
+ mCurrentTime += nanos;
+ }
+ }
+}
diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index 189be53a397f..786c03ba5150 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -87,18 +87,22 @@ public final class FrameInfo {
// When the frame needs to be ready by
public static final int FRAME_DEADLINE = 9;
+ // When frame actually started.
+ public static final int FRAME_START_TIME = 10;
+
// Must be the last one
// This value must be in sync with `UI_THREAD_FRAME_INFO_SIZE` in FrameInfo.h
- private static final int FRAME_INFO_SIZE = FRAME_DEADLINE + 1;
+ private static final int FRAME_INFO_SIZE = FRAME_START_TIME + 1;
/** checkstyle */
public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId,
- long frameDeadline) {
+ long frameDeadline, long frameStartTime) {
frameInfo[FRAME_TIMELINE_VSYNC_ID] = frameTimelineVsyncId;
frameInfo[INTENDED_VSYNC] = intendedVsync;
frameInfo[VSYNC] = usedVsync;
frameInfo[FLAGS] = 0;
frameInfo[FRAME_DEADLINE] = frameDeadline;
+ frameInfo[FRAME_START_TIME] = frameStartTime;
}
/** checkstyle */
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 88cf96a9eb4e..7589435fc3b7 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -28,6 +28,7 @@ import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.PerformanceHintManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
@@ -165,7 +166,7 @@ public class HardwareRenderer {
* to opaque with no light source configured.
*/
public HardwareRenderer() {
- ProcessInitializer.sInstance.initDisplayInfo();
+ ProcessInitializer.sInstance.initUsingContext();
mRootNode = RenderNode.adopt(nCreateRootRenderNode());
mRootNode.setClipToBounds(false);
mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode);
@@ -365,7 +366,8 @@ public class HardwareRenderer {
*/
public @NonNull FrameRenderRequest setVsyncTime(long vsyncTime) {
// TODO(b/168552873): populate vsync Id once available to Choreographer public API
- mFrameInfo.setVsync(vsyncTime, vsyncTime, FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE);
+ mFrameInfo.setVsync(vsyncTime, vsyncTime, FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE,
+ vsyncTime);
mFrameInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS);
return this;
}
@@ -835,6 +837,36 @@ public class HardwareRenderer {
callback.onPictureCaptured(picture);
}
+ /** called by native */
+ static PerformanceHintManager.Session createHintSession(int[] tids) {
+ PerformanceHintManager performanceHintManager =
+ ProcessInitializer.sInstance.getHintManager();
+ if (performanceHintManager == null) {
+ return null;
+ }
+ // Native code will always set a target duration before reporting actual durations.
+ // So this is just a placeholder value that's never used.
+ long targetDurationNanos = 16666667;
+ return performanceHintManager.createHintSession(tids, targetDurationNanos);
+ }
+
+ /** called by native */
+ static void updateTargetWorkDuration(PerformanceHintManager.Session session,
+ long targetDurationNanos) {
+ session.updateTargetWorkDuration(targetDurationNanos);
+ }
+
+ /** called by native */
+ static void reportActualWorkDuration(PerformanceHintManager.Session session,
+ long actualDurationNanos) {
+ session.reportActualWorkDuration(actualDurationNanos);
+ }
+
+ /** called by native */
+ static void closeHintSession(PerformanceHintManager.Session session) {
+ session.close();
+ }
+
/**
* Interface used to receive callbacks when a frame is being drawn.
*
@@ -1071,6 +1103,7 @@ public class HardwareRenderer {
private boolean mIsolated = false;
private Context mContext;
private String mPackageName;
+ private PerformanceHintManager mPerformanceHintManager;
private IGraphicsStats mGraphicsStatsService;
private IGraphicsStatsCallback mGraphicsStatsCallback = new IGraphicsStatsCallback.Stub() {
@Override
@@ -1082,6 +1115,10 @@ public class HardwareRenderer {
private ProcessInitializer() {
}
+ synchronized PerformanceHintManager getHintManager() {
+ return mPerformanceHintManager;
+ }
+
synchronized void setPackageName(String name) {
if (mInitialized) return;
mPackageName = name;
@@ -1127,15 +1164,23 @@ public class HardwareRenderer {
}
}
- synchronized void initDisplayInfo() {
- if (mDisplayInitialized) return;
+ synchronized void initUsingContext() {
if (mContext == null) return;
- // If we're in an isolated sandbox mode then we shouldn't try to communicate with DMS
+ initDisplayInfo();
+
+ // HintManager and HintSession are designed to be accessible from isoalted processes
+ // so not checking for isolated process here.
+ initHintSession();
+
+ // Defensively clear out the context in case we were passed a context that can leak
+ // if we live longer than it, e.g. an activity context.
+ mContext = null;
+ }
+
+ private void initDisplayInfo() {
+ if (mDisplayInitialized) return;
if (mIsolated) {
- // Defensively clear out the context in case we were passed a context that can leak
- // if we live longer than it, e.g. an activity context.
- mContext = null;
mDisplayInitialized = true;
return;
}
@@ -1167,11 +1212,14 @@ public class HardwareRenderer {
display.getRefreshRate(), wideColorDataspace.mNativeDataspace,
display.getAppVsyncOffsetNanos(), display.getPresentationDeadlineNanos());
- // Defensively clear out the context
- mContext = null;
mDisplayInitialized = true;
}
+ private void initHintSession() {
+ if (mContext == null) return;
+ mPerformanceHintManager = mContext.getSystemService(PerformanceHintManager.class);
+ }
+
private void rotateBuffer() {
nRotateProcessStatsBuffer();
requestBuffer();
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index 2448cc904104..51fbf363f51c 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -21,29 +21,16 @@ namespace android {
namespace uirenderer {
const std::array FrameInfoNames{
- "Flags",
- "FrameTimelineVsyncId",
- "IntendedVsync",
- "Vsync",
- "InputEventId",
- "HandleInputStart",
- "AnimationStart",
- "PerformTraversalsStart",
- "DrawStart",
- "FrameDeadline",
- "SyncQueued",
- "SyncStart",
- "IssueDrawCommandsStart",
- "SwapBuffers",
- "FrameCompleted",
- "DequeueBufferDuration",
- "QueueBufferDuration",
- "GpuCompleted",
- "SwapBuffersCompleted",
- "DisplayPresentTime",
+ "Flags", "FrameTimelineVsyncId", "IntendedVsync",
+ "Vsync", "InputEventId", "HandleInputStart",
+ "AnimationStart", "PerformTraversalsStart", "DrawStart",
+ "FrameDeadline", "FrameStartTime", "SyncQueued",
+ "SyncStart", "IssueDrawCommandsStart", "SwapBuffers",
+ "FrameCompleted", "DequeueBufferDuration", "QueueBufferDuration",
+ "GpuCompleted", "SwapBuffersCompleted", "DisplayPresentTime",
};
-static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 20,
+static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 21,
"Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
void FrameInfo::importUiThreadInfo(int64_t* info) {
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index e9b2f4a9bb3b..62ac4ca5fdad 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -28,7 +28,7 @@
namespace android {
namespace uirenderer {
-static constexpr size_t UI_THREAD_FRAME_INFO_SIZE = 10;
+static constexpr size_t UI_THREAD_FRAME_INFO_SIZE = 11;
enum class FrameInfoIndex {
Flags = 0,
@@ -41,6 +41,7 @@ enum class FrameInfoIndex {
PerformTraversalsStart,
DrawStart,
FrameDeadline,
+ FrameStartTime,
// End of UI frame info
SyncQueued,
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index e58f31fd15eb..7af0a221d228 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -81,6 +81,9 @@ bool Properties::isolatedProcess = false;
int Properties::contextPriority = 0;
float Properties::defaultSdrWhitePoint = 200.f;
+bool Properties::useHintManager = true;
+int Properties::targetCpuTimePercentage = 70;
+
bool Properties::load() {
bool prevDebugLayersUpdates = debugLayersUpdates;
bool prevDebugOverdraw = debugOverdraw;
@@ -128,6 +131,10 @@ bool Properties::load() {
runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false);
+ useHintManager = base::GetBoolProperty(PROPERTY_USE_HINT_MANAGER, true);
+ targetCpuTimePercentage = base::GetIntProperty(PROPERTY_TARGET_CPU_TIME_PERCENTAGE, 70);
+ if (targetCpuTimePercentage <= 0 || targetCpuTimePercentage > 100) targetCpuTimePercentage = 70;
+
return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index ea9cbd592d29..1cb87be98051 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -158,6 +158,20 @@ enum DebugLevel {
#define PROPERTY_CAPTURE_SKP_FILENAME "debug.hwui.skp_filename"
/**
+ * Controls whether HWUI will send timing hints to HintManager for
+ * better CPU scheduling. Accepted values are "true" and "false".
+ */
+#define PROPERTY_USE_HINT_MANAGER "debug.hwui.use_hint_manager"
+
+/**
+ * Percentage of frame time that's used for CPU work. The rest is
+ * reserved for GPU work. This is used with use_hint_manager to
+ * provide timing hints to HintManager. Accepted values are
+ * integer from 1-100.
+ */
+#define PROPERTY_TARGET_CPU_TIME_PERCENTAGE "debug.hwui.target_cpu_time_percent"
+
+/**
* Property for whether this is running in the emulator.
*/
#define PROPERTY_IS_EMULATOR "ro.boot.qemu"
@@ -253,6 +267,9 @@ public:
static float defaultSdrWhitePoint;
+ static bool useHintManager;
+ static int targetCpuTimePercentage;
+
private:
static ProfileType sProfileType;
static bool sDisableProfileBars;
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index f24ba5c1c878..bd1da985a33e 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -34,14 +34,18 @@
#include <renderthread/RenderProxy.h>
#include <renderthread/RenderTask.h>
#include <renderthread/RenderThread.h>
+#include <thread/CommonPool.h>
#include <utils/Color.h>
#include <utils/RefBase.h>
#include <utils/StrongPointer.h>
#include <utils/Timers.h>
#include <utils/TraceUtils.h>
+#include <pthread.h>
+
#include <algorithm>
#include <atomic>
+#include <vector>
#include "android_graphics_HardwareRendererObserver.h"
@@ -53,6 +57,10 @@ using namespace android::uirenderer::renderthread;
struct {
jclass clazz;
jmethodID invokePictureCapturedCallback;
+ jmethodID createHintSession;
+ jmethodID updateTargetWorkDuration;
+ jmethodID reportActualWorkDuration;
+ jmethodID closeHintSession;
} gHardwareRenderer;
struct {
@@ -71,6 +79,14 @@ static JNIEnv* getenv(JavaVM* vm) {
return env;
}
+static bool hasExceptionAndClear(JNIEnv* env) {
+ if (GraphicsJNI::hasException(env)) {
+ env->ExceptionClear();
+ return true;
+ }
+ return false;
+}
+
typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface);
ANW_fromSurface fromSurface;
@@ -120,6 +136,67 @@ private:
}
};
+class HintSessionWrapper : public LightRefBase<HintSessionWrapper> {
+public:
+ static sp<HintSessionWrapper> create(JNIEnv* env, RenderProxy* proxy) {
+ if (!Properties::useHintManager) return nullptr;
+
+ // Include UI thread (self), render thread, and thread pool.
+ std::vector<int> tids = CommonPool::getThreadIds();
+ tids.push_back(proxy->getRenderThreadTid());
+ tids.push_back(pthread_gettid_np(pthread_self()));
+
+ jintArray tidsArray = env->NewIntArray(tids.size());
+ if (hasExceptionAndClear(env)) return nullptr;
+ env->SetIntArrayRegion(tidsArray, 0, tids.size(), reinterpret_cast<jint*>(tids.data()));
+ if (hasExceptionAndClear(env)) return nullptr;
+ jobject session = env->CallStaticObjectMethod(
+ gHardwareRenderer.clazz, gHardwareRenderer.createHintSession, tidsArray);
+ if (hasExceptionAndClear(env) || !session) return nullptr;
+ return new HintSessionWrapper(env, session);
+ }
+
+ ~HintSessionWrapper() {
+ if (!mSession) return;
+ JNIEnv* env = getenv(mVm);
+ env->CallStaticVoidMethod(gHardwareRenderer.clazz, gHardwareRenderer.closeHintSession,
+ mSession);
+ hasExceptionAndClear(env);
+ env->DeleteGlobalRef(mSession);
+ mSession = nullptr;
+ }
+
+ void updateTargetWorkDuration(long targetDurationNanos) {
+ if (!mSession) return;
+ JNIEnv* env = getenv(mVm);
+ env->CallStaticVoidMethod(gHardwareRenderer.clazz,
+ gHardwareRenderer.updateTargetWorkDuration, mSession,
+ static_cast<jlong>(targetDurationNanos));
+ hasExceptionAndClear(env);
+ }
+
+ void reportActualWorkDuration(long actualDurationNanos) {
+ if (!mSession) return;
+ JNIEnv* env = getenv(mVm);
+ env->CallStaticVoidMethod(gHardwareRenderer.clazz,
+ gHardwareRenderer.reportActualWorkDuration, mSession,
+ static_cast<jlong>(actualDurationNanos));
+ hasExceptionAndClear(env);
+ }
+
+private:
+ HintSessionWrapper(JNIEnv* env, jobject jobject) {
+ env->GetJavaVM(&mVm);
+ if (jobject) {
+ mSession = env->NewGlobalRef(jobject);
+ LOG_ALWAYS_FATAL_IF(!mSession, "Failed to make global ref");
+ }
+ }
+
+ JavaVM* mVm = nullptr;
+ jobject mSession = nullptr;
+};
+
static void android_view_ThreadedRenderer_rotateProcessStatsBuffer(JNIEnv* env, jobject clazz) {
RenderProxy::rotateProcessStatsBuffer();
}
@@ -147,6 +224,12 @@ static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject claz
RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr);
ContextFactoryImpl factory(rootRenderNode);
RenderProxy* proxy = new RenderProxy(translucent, rootRenderNode, &factory);
+ sp<HintSessionWrapper> wrapper = HintSessionWrapper::create(env, proxy);
+ if (wrapper) {
+ proxy->setHintSessionCallbacks(
+ [wrapper](int64_t nanos) { wrapper->updateTargetWorkDuration(nanos); },
+ [wrapper](int64_t nanos) { wrapper->reportActualWorkDuration(nanos); });
+ }
return (jlong) proxy;
}
@@ -769,6 +852,18 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) {
gHardwareRenderer.invokePictureCapturedCallback = GetStaticMethodIDOrDie(env, hardwareRenderer,
"invokePictureCapturedCallback",
"(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V");
+ gHardwareRenderer.createHintSession =
+ GetStaticMethodIDOrDie(env, hardwareRenderer, "createHintSession",
+ "([I)Landroid/os/PerformanceHintManager$Session;");
+ gHardwareRenderer.updateTargetWorkDuration =
+ GetStaticMethodIDOrDie(env, hardwareRenderer, "updateTargetWorkDuration",
+ "(Landroid/os/PerformanceHintManager$Session;J)V");
+ gHardwareRenderer.reportActualWorkDuration =
+ GetStaticMethodIDOrDie(env, hardwareRenderer, "reportActualWorkDuration",
+ "(Landroid/os/PerformanceHintManager$Session;J)V");
+ gHardwareRenderer.closeHintSession =
+ GetStaticMethodIDOrDie(env, hardwareRenderer, "closeHintSession",
+ "(Landroid/os/PerformanceHintManager$Session;)V");
jclass frameCallbackClass = FindClassOrDie(env,
"android/graphics/HardwareRenderer$FrameDrawingCallback");
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 3408ffda3f9d..7a38a3bc9c05 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -21,6 +21,7 @@
#include "../DeferredLayerUpdater.h"
#include "../DisplayList.h"
+#include "../Properties.h"
#include "../RenderNode.h"
#include "CanvasContext.h"
#include "RenderThread.h"
@@ -44,6 +45,12 @@ void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context,
mTargetNode = targetNode;
}
+void DrawFrameTask::setHintSessionCallbacks(std::function<void(int64_t)> updateTargetWorkDuration,
+ std::function<void(int64_t)> reportActualWorkDuration) {
+ mUpdateTargetWorkDuration = std::move(updateTargetWorkDuration);
+ mReportActualWorkDuration = std::move(reportActualWorkDuration);
+}
+
void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) {
LOG_ALWAYS_FATAL_IF(!mContext,
"Lifecycle violation, there's no context to pushLayerUpdate with!");
@@ -102,6 +109,9 @@ void DrawFrameTask::run() {
CanvasContext* context = mContext;
std::function<void(int64_t)> callback = std::move(mFrameCallback);
mFrameCallback = nullptr;
+ int64_t intendedVsync = mFrameInfo[static_cast<int>(FrameInfoIndex::IntendedVsync)];
+ int64_t frameDeadline = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameDeadline)];
+ int64_t frameStartTime = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameStartTime)];
// From this point on anything in "this" is *UNSAFE TO ACCESS*
if (canUnblockUiThread) {
@@ -124,6 +134,25 @@ void DrawFrameTask::run() {
if (!canUnblockUiThread) {
unblockUiThread();
}
+
+ // These member callbacks are effectively const as they are set once during init, so it's safe
+ // to use these directly instead of making local copies.
+ if (mUpdateTargetWorkDuration && mReportActualWorkDuration) {
+ constexpr int64_t kSanityCheckLowerBound = 100000; // 0.1ms
+ constexpr int64_t kSanityCheckUpperBound = 10000000000; // 10s
+ int64_t targetWorkDuration = frameDeadline - intendedVsync;
+ targetWorkDuration = targetWorkDuration * Properties::targetCpuTimePercentage / 100;
+ if (targetWorkDuration > kSanityCheckLowerBound &&
+ targetWorkDuration < kSanityCheckUpperBound &&
+ targetWorkDuration != mLastTargetWorkDuration) {
+ mLastTargetWorkDuration = targetWorkDuration;
+ mUpdateTargetWorkDuration(targetWorkDuration);
+ }
+ int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
+ if (frameDuration > kSanityCheckLowerBound && frameDuration < kSanityCheckUpperBound) {
+ mReportActualWorkDuration(frameDuration);
+ }
+ }
}
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 696cfaef3cd7..3bb574aff59b 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -61,6 +61,8 @@ public:
virtual ~DrawFrameTask();
void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode);
+ void setHintSessionCallbacks(std::function<void(int64_t)> updateTargetWorkDuration,
+ std::function<void(int64_t)> reportActualWorkDuration);
void setContentDrawBounds(int left, int top, int right, int bottom) {
mContentDrawBounds.set(left, top, right, bottom);
}
@@ -107,6 +109,10 @@ private:
std::function<void(int64_t)> mFrameCallback;
std::function<void(int64_t)> mFrameCompleteCallback;
+
+ nsecs_t mLastTargetWorkDuration = 0;
+ std::function<void(int64_t)> mUpdateTargetWorkDuration;
+ std::function<void(int64_t)> mReportActualWorkDuration;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 423cc08189ca..9361abd20852 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -76,6 +76,12 @@ void RenderProxy::setName(const char* name) {
mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); });
}
+void RenderProxy::setHintSessionCallbacks(std::function<void(int64_t)> updateTargetWorkDuration,
+ std::function<void(int64_t)> reportActualWorkDuration) {
+ mDrawFrameTask.setHintSessionCallbacks(std::move(updateTargetWorkDuration),
+ std::move(reportActualWorkDuration));
+}
+
void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
if (window) { ANativeWindow_acquire(window); }
mRenderThread.queue().post([this, win = window, enableTimeout]() mutable {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 366d6b5a172c..8d55d3c55570 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -71,6 +71,8 @@ public:
void setSwapBehavior(SwapBehavior swapBehavior);
bool loadSystemProperties();
void setName(const char* name);
+ void setHintSessionCallbacks(std::function<void(int64_t)> updateTargetWorkDuration,
+ std::function<void(int64_t)> reportActualWorkDuration);
void setSurface(ANativeWindow* window, bool enableTimeout = true);
void setSurfaceControl(ASurfaceControl* surfaceControl);
diff --git a/libs/hwui/thread/CommonPool.cpp b/libs/hwui/thread/CommonPool.cpp
index d011bdfe945e..dc92f9f0d39a 100644
--- a/libs/hwui/thread/CommonPool.cpp
+++ b/libs/hwui/thread/CommonPool.cpp
@@ -29,14 +29,23 @@ CommonPool::CommonPool() {
ATRACE_CALL();
CommonPool* pool = this;
+ std::mutex mLock;
+ std::vector<int> tids(THREAD_COUNT);
+ std::vector<std::condition_variable> tidConditionVars(THREAD_COUNT);
+
// Create 2 workers
for (int i = 0; i < THREAD_COUNT; i++) {
- std::thread worker([pool, i] {
+ std::thread worker([pool, i, &mLock, &tids, &tidConditionVars] {
{
std::array<char, 20> name{"hwuiTask"};
snprintf(name.data(), name.size(), "hwuiTask%d", i);
auto self = pthread_self();
pthread_setname_np(self, name.data());
+ {
+ std::unique_lock lock(mLock);
+ tids[i] = pthread_gettid_np(self);
+ tidConditionVars[i].notify_one();
+ }
setpriority(PRIO_PROCESS, 0, PRIORITY_FOREGROUND);
auto startHook = renderthread::RenderThread::getOnStartHook();
if (startHook) {
@@ -47,6 +56,15 @@ CommonPool::CommonPool() {
});
worker.detach();
}
+ {
+ std::unique_lock lock(mLock);
+ for (int i = 0; i < THREAD_COUNT; i++) {
+ while (!tids[i]) {
+ tidConditionVars[i].wait(lock);
+ }
+ }
+ }
+ mWorkerThreadIds = std::move(tids);
}
CommonPool& CommonPool::instance() {
@@ -58,6 +76,10 @@ void CommonPool::post(Task&& task) {
instance().enqueue(std::move(task));
}
+std::vector<int> CommonPool::getThreadIds() {
+ return instance().mWorkerThreadIds;
+}
+
void CommonPool::enqueue(Task&& task) {
std::unique_lock lock(mLock);
while (!mWorkQueue.hasSpace()) {
@@ -104,4 +126,4 @@ void CommonPool::doWaitForIdle() {
}
} // namespace uirenderer
-} // namespace android \ No newline at end of file
+} // namespace android
diff --git a/libs/hwui/thread/CommonPool.h b/libs/hwui/thread/CommonPool.h
index 7603eeef4692..74f852bd1413 100644
--- a/libs/hwui/thread/CommonPool.h
+++ b/libs/hwui/thread/CommonPool.h
@@ -25,6 +25,7 @@
#include <functional>
#include <future>
#include <mutex>
+#include <vector>
namespace android {
namespace uirenderer {
@@ -97,6 +98,8 @@ public:
return task.get_future().get();
};
+ static std::vector<int> getThreadIds();
+
// For testing purposes only, blocks until all worker threads are parked.
static void waitForIdle();
@@ -111,6 +114,8 @@ private:
void workerLoop();
+ std::vector<int> mWorkerThreadIds;
+
std::mutex mLock;
std::condition_variable mCondition;
int mWaitingThreads = 0;
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
new file mode 100644
index 000000000000..fc7628c28b8b
--- /dev/null
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -0,0 +1,449 @@
+/*
+ * 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 android.app.ActivityManager;
+import android.app.IUidObserver;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IHintManager;
+import android.os.IHintSession;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+import com.android.server.SystemService;
+import com.android.server.utils.Slogf;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+/** An hint service implementation that runs in System Server process. */
+public final class HintManagerService extends SystemService {
+ private static final String TAG = "HintManagerService";
+ private static final boolean DEBUG = false;
+ @VisibleForTesting final long mHintSessionPreferredRate;
+
+ @GuardedBy("mLock")
+ private final ArrayMap<Integer, ArrayMap<IBinder, AppHintSession>> mActiveSessions;
+
+ /** Lock to protect HAL handles and listen list. */
+ private final Object mLock = new Object();
+
+ @VisibleForTesting final UidObserver mUidObserver;
+
+ private final NativeWrapper mNativeWrapper;
+
+ @VisibleForTesting final IHintManager.Stub mService = new BinderService();
+
+ public HintManagerService(Context context) {
+ this(context, new Injector());
+ }
+
+ @VisibleForTesting
+ HintManagerService(Context context, Injector injector) {
+ super(context);
+ mActiveSessions = new ArrayMap<>();
+ mNativeWrapper = injector.createNativeWrapper();
+ mNativeWrapper.halInit();
+ mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate();
+ mUidObserver = new UidObserver();
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ NativeWrapper createNativeWrapper() {
+ return new NativeWrapper();
+ }
+ }
+
+ private boolean isHalSupported() {
+ return mHintSessionPreferredRate != -1;
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.PERFORMANCE_HINT_SERVICE, mService, /* allowIsolated= */ true);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ systemReady();
+ }
+ }
+
+ private void systemReady() {
+ Slogf.v(TAG, "Initializing HintManager service...");
+ try {
+ ActivityManager.getService().registerUidObserver(mUidObserver,
+ ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
+ ActivityManager.PROCESS_STATE_UNKNOWN, null);
+ } catch (RemoteException e) {
+ // ignored; both services live in system_server
+ }
+
+ }
+
+ /**
+ * Wrapper around the static-native methods from native.
+ *
+ * This class exists to allow us to mock static native methods in our tests. If mocking static
+ * methods becomes easier than this in the future, we can delete this class.
+ */
+ @VisibleForTesting
+ public static class NativeWrapper {
+ private native void nativeInit();
+
+ private static native long nativeCreateHintSession(int tgid, int uid, int[] tids,
+ long durationNanos);
+
+ private static native void nativePauseHintSession(long halPtr);
+
+ private static native void nativeResumeHintSession(long halPtr);
+
+ private static native void nativeCloseHintSession(long halPtr);
+
+ private static native void nativeUpdateTargetWorkDuration(
+ long halPtr, long targetDurationNanos);
+
+ private static native void nativeReportActualWorkDuration(
+ long halPtr, long[] actualDurationNanos, long[] timeStampNanos);
+
+ private static native long nativeGetHintSessionPreferredRate();
+
+ /** Wrapper for HintManager.nativeInit */
+ public void halInit() {
+ nativeInit();
+ }
+
+ /** Wrapper for HintManager.nativeCreateHintSession */
+ public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) {
+ return nativeCreateHintSession(tgid, uid, tids, durationNanos);
+ }
+
+ /** Wrapper for HintManager.nativePauseHintSession */
+ public void halPauseHintSession(long halPtr) {
+ nativePauseHintSession(halPtr);
+ }
+
+ /** Wrapper for HintManager.nativeResumeHintSession */
+ public void halResumeHintSession(long halPtr) {
+ nativeResumeHintSession(halPtr);
+ }
+
+ /** Wrapper for HintManager.nativeCloseHintSession */
+ public void halCloseHintSession(long halPtr) {
+ nativeCloseHintSession(halPtr);
+ }
+
+ /** Wrapper for HintManager.nativeUpdateTargetWorkDuration */
+ public void halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos) {
+ nativeUpdateTargetWorkDuration(halPtr, targetDurationNanos);
+ }
+
+ /** Wrapper for HintManager.nativeReportActualWorkDuration */
+ public void halReportActualWorkDuration(
+ long halPtr, long[] actualDurationNanos, long[] timeStampNanos) {
+ nativeReportActualWorkDuration(halPtr, actualDurationNanos,
+ timeStampNanos);
+ }
+
+ /** Wrapper for HintManager.nativeGetHintSessionPreferredRate */
+ public long halGetHintSessionPreferredRate() {
+ return nativeGetHintSessionPreferredRate();
+ }
+ }
+
+ @VisibleForTesting
+ final class UidObserver extends IUidObserver.Stub {
+ private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
+
+ public boolean isUidForeground(int uid) {
+ synchronized (mLock) {
+ return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
+ <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ }
+ }
+
+ @Override
+ public void onUidGone(int uid, boolean disabled) {
+ FgThread.getHandler().post(() -> {
+ synchronized (mLock) {
+ ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid);
+ if (tokenMap == null) {
+ return;
+ }
+ for (int i = tokenMap.size() - 1; i >= 0; i--) {
+ // Will remove the session from tokenMap
+ tokenMap.valueAt(i).close();
+ }
+ mProcStatesCache.delete(uid);
+ }
+ });
+ }
+
+ @Override
+ public void onUidActive(int uid) {
+ }
+
+ @Override
+ public void onUidIdle(int uid, boolean disabled) {
+ }
+
+ /**
+ * The IUidObserver callback is called from the system_server, so it'll be a direct function
+ * call from ActivityManagerService. Do not do heavy logic here.
+ */
+ @Override
+ public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
+ FgThread.getHandler().post(() -> {
+ synchronized (mLock) {
+ mProcStatesCache.put(uid, procState);
+ ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid);
+ if (tokenMap == null) {
+ return;
+ }
+ for (AppHintSession s : tokenMap.values()) {
+ s.onProcStateChanged();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onUidCachedChanged(int uid, boolean cached) {
+ }
+ }
+
+ @VisibleForTesting
+ IHintManager.Stub getBinderServiceInstance() {
+ return mService;
+ }
+
+ private boolean checkTidValid(int tgid, int [] tids) {
+ // Make sure all tids belongs to the same process.
+ for (int threadId : tids) {
+ if (!Process.isThreadInProcess(tgid, threadId)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ final class BinderService extends IHintManager.Stub {
+ @Override
+ public IHintSession createHintSession(IBinder token, int[] tids, long durationNanos) {
+ if (!isHalSupported()) return null;
+
+ java.util.Objects.requireNonNull(token);
+ java.util.Objects.requireNonNull(tids);
+ Preconditions.checkArgument(tids.length != 0, "tids should"
+ + " not be empty.");
+
+ int uid = Binder.getCallingUid();
+ int tid = Binder.getCallingPid();
+ int pid = Process.getThreadGroupLeader(tid);
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (!checkTidValid(pid, tids)) {
+ throw new SecurityException("Some tid doesn't belong to the process");
+ }
+
+ long halSessionPtr = mNativeWrapper.halCreateHintSession(pid, uid, tids,
+ durationNanos);
+ if (halSessionPtr == 0) return null;
+
+ AppHintSession hs = new AppHintSession(uid, pid, tids, token,
+ halSessionPtr, durationNanos);
+ synchronized (mLock) {
+ ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid);
+ if (tokenMap == null) {
+ tokenMap = new ArrayMap<>(1);
+ mActiveSessions.put(uid, tokenMap);
+ }
+ tokenMap.put(token, hs);
+ return hs;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public long getHintSessionPreferredRate() {
+ return mHintSessionPreferredRate;
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
+ return;
+ }
+ synchronized (mLock) {
+ pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate);
+ pw.println("HAL Support: " + isHalSupported());
+ pw.println("Active Sessions:");
+ for (int i = 0; i < mActiveSessions.size(); i++) {
+ pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":");
+ ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.valueAt(i);
+ for (int j = 0; j < tokenMap.size(); j++) {
+ pw.println(" Session " + j + ":");
+ tokenMap.valueAt(j).dump(pw, " ");
+ }
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient {
+ protected final int mUid;
+ protected final int mPid;
+ protected final int[] mThreadIds;
+ protected final IBinder mToken;
+ protected long mHalSessionPtr;
+ protected long mTargetDurationNanos;
+ protected boolean mUpdateAllowed;
+
+ protected AppHintSession(
+ int uid, int pid, int[] threadIds, IBinder token,
+ long halSessionPtr, long durationNanos) {
+ mUid = uid;
+ mPid = pid;
+ mToken = token;
+ mThreadIds = threadIds;
+ mHalSessionPtr = halSessionPtr;
+ mTargetDurationNanos = durationNanos;
+ mUpdateAllowed = true;
+ updateHintAllowed();
+ try {
+ token.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ mNativeWrapper.halCloseHintSession(mHalSessionPtr);
+ throw new IllegalStateException("Client already dead", e);
+ }
+ }
+
+ @VisibleForTesting
+ boolean updateHintAllowed() {
+ synchronized (mLock) {
+ final boolean allowed = mUidObserver.isUidForeground(mUid);
+ if (allowed && !mUpdateAllowed) resume();
+ if (!allowed && mUpdateAllowed) pause();
+ mUpdateAllowed = allowed;
+ return mUpdateAllowed;
+ }
+ }
+
+ @Override
+ public void updateTargetWorkDuration(long targetDurationNanos) {
+ synchronized (mLock) {
+ if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+ return;
+ }
+ Preconditions.checkArgument(targetDurationNanos > 0, "Expected"
+ + " the target duration to be greater than 0.");
+ mNativeWrapper.halUpdateTargetWorkDuration(mHalSessionPtr, targetDurationNanos);
+ mTargetDurationNanos = targetDurationNanos;
+ }
+ }
+
+ @Override
+ public void reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos) {
+ synchronized (mLock) {
+ if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+ return;
+ }
+ Preconditions.checkArgument(actualDurationNanos.length != 0, "the count"
+ + " of hint durations shouldn't be 0.");
+ Preconditions.checkArgument(actualDurationNanos.length == timeStampNanos.length,
+ "The length of durations and timestamps should be the same.");
+ for (int i = 0; i < actualDurationNanos.length; i++) {
+ if (actualDurationNanos[i] <= 0) {
+ throw new IllegalArgumentException(
+ String.format("durations[%d]=%d should be greater than 0",
+ i, actualDurationNanos[i]));
+ }
+ }
+ mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, actualDurationNanos,
+ timeStampNanos);
+ }
+ }
+
+ /** TODO: consider monitor session threads and close session if any thread is dead. */
+ @Override
+ public void close() {
+ synchronized (mLock) {
+ if (mHalSessionPtr == 0) return;
+ mNativeWrapper.halCloseHintSession(mHalSessionPtr);
+ mHalSessionPtr = 0;
+ mToken.unlinkToDeath(this, 0);
+ ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(mUid);
+ if (tokenMap == null) {
+ Slogf.w(TAG, "UID %d is note present in active session map", mUid);
+ }
+ tokenMap.remove(mToken);
+ if (tokenMap.isEmpty()) mActiveSessions.remove(mUid);
+ }
+ }
+
+ private void onProcStateChanged() {
+ updateHintAllowed();
+ }
+
+ private void pause() {
+ synchronized (mLock) {
+ if (mHalSessionPtr == 0) return;
+ mNativeWrapper.halPauseHintSession(mHalSessionPtr);
+ }
+ }
+
+ private void resume() {
+ synchronized (mLock) {
+ if (mHalSessionPtr == 0) return;
+ mNativeWrapper.halResumeHintSession(mHalSessionPtr);
+ }
+ }
+
+ private void dump(PrintWriter pw, String prefix) {
+ synchronized (mLock) {
+ pw.println(prefix + "SessionPID: " + mPid);
+ pw.println(prefix + "SessionUID: " + mUid);
+ pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds));
+ pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos);
+ pw.println(prefix + "SessionAllowed: " + updateHintAllowed());
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ close();
+ }
+
+ }
+}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 15f57653840b..a99679ade958 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -49,6 +49,7 @@ cc_library_static {
"com_android_server_net_NetworkStatsService.cpp",
"com_android_server_power_PowerManagerService.cpp",
"com_android_server_powerstats_PowerStatsService.cpp",
+ "com_android_server_hint_HintManagerService.cpp",
"com_android_server_SerialService.cpp",
"com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
"com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp",
@@ -158,7 +159,7 @@ cc_defaults {
"android.hardware.memtrack-V1-ndk_platform",
"android.hardware.power@1.0",
"android.hardware.power@1.1",
- "android.hardware.power-V1-cpp",
+ "android.hardware.power-V2-cpp",
"android.hardware.power.stats@1.0",
"android.hardware.power.stats-V1-ndk_platform",
"android.hardware.thermal@1.0",
@@ -195,8 +196,8 @@ cc_defaults {
"libchrome",
"libmojo",
],
- }
- }
+ },
+ },
}
filegroup {
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
new file mode 100644
index 000000000000..000cb839002b
--- /dev/null
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+#define TAG "HintManagerService-JNI"
+
+//#define LOG_NDEBUG 0
+
+#include <android-base/stringprintf.h>
+#include <android/hardware/power/IPower.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <powermanager/PowerHalController.h>
+#include <utils/Log.h>
+
+#include <unistd.h>
+#include <cinttypes>
+
+#include <sys/types.h>
+
+#include "jni.h"
+
+using android::hardware::power::IPowerHintSession;
+using android::hardware::power::WorkDuration;
+
+using android::base::StringPrintf;
+
+namespace android {
+
+static power::PowerHalController gPowerHalController;
+
+static jlong createHintSession(JNIEnv* env, int32_t tgid, int32_t uid,
+ std::vector<int32_t> threadIds, int64_t durationNanos) {
+ auto result =
+ gPowerHalController.createHintSession(tgid, uid, std::move(threadIds), durationNanos);
+ if (result.isOk()) {
+ sp<IPowerHintSession> appSession = result.value();
+ if (appSession) appSession->incStrong(env);
+ return reinterpret_cast<jlong>(appSession.get());
+ }
+ return 0;
+}
+
+static void pauseHintSession(JNIEnv* env, int64_t session_ptr) {
+ sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ appSession->pause();
+}
+
+static void resumeHintSession(JNIEnv* env, int64_t session_ptr) {
+ sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ appSession->resume();
+}
+
+static void closeHintSession(JNIEnv* env, int64_t session_ptr) {
+ sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ appSession->close();
+ appSession->decStrong(env);
+}
+
+static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDurationNanos) {
+ sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ appSession->updateTargetWorkDuration(targetDurationNanos);
+}
+
+static void reportActualWorkDuration(int64_t session_ptr,
+ const std::vector<WorkDuration>& actualDurations) {
+ sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ appSession->reportActualWorkDuration(actualDurations);
+}
+
+static int64_t getHintSessionPreferredRate() {
+ int64_t rate = -1;
+ auto result = gPowerHalController.getHintSessionPreferredRate();
+ if (result.isOk()) {
+ rate = result.value();
+ }
+ return rate;
+}
+
+// ----------------------------------------------------------------------------
+static void nativeInit(JNIEnv* env, jobject obj) {
+ gPowerHalController.init();
+}
+
+static jlong nativeCreateHintSession(JNIEnv* env, jclass /* clazz */, jint tgid, jint uid,
+ jintArray tids, jlong durationNanos) {
+ ScopedIntArrayRO tidArray(env, tids);
+ if (nullptr == tidArray.get() || tidArray.size() == 0) {
+ ALOGW("GetIntArrayElements returns nullptr.");
+ return 0;
+ }
+ std::vector<int32_t> threadIds(tidArray.size());
+ for (size_t i = 0; i < tidArray.size(); i++) {
+ threadIds[i] = tidArray[i];
+ }
+ return createHintSession(env, tgid, uid, std::move(threadIds), durationNanos);
+}
+
+static void nativePauseHintSession(JNIEnv* env, jclass /* clazz */, jlong session_ptr) {
+ pauseHintSession(env, session_ptr);
+}
+
+static void nativeResumeHintSession(JNIEnv* env, jclass /* clazz */, jlong session_ptr) {
+ resumeHintSession(env, session_ptr);
+}
+
+static void nativeCloseHintSession(JNIEnv* env, jclass /* clazz */, jlong session_ptr) {
+ closeHintSession(env, session_ptr);
+}
+
+static void nativeUpdateTargetWorkDuration(JNIEnv* /* env */, jclass /* clazz */, jlong session_ptr,
+ jlong targetDurationNanos) {
+ updateTargetWorkDuration(session_ptr, targetDurationNanos);
+}
+
+static void nativeReportActualWorkDuration(JNIEnv* env, jclass /* clazz */, jlong session_ptr,
+ jlongArray actualDurations, jlongArray timeStamps) {
+ ScopedLongArrayRO arrayActualDurations(env, actualDurations);
+ ScopedLongArrayRO arrayTimeStamps(env, timeStamps);
+
+ std::vector<WorkDuration> actualList(arrayActualDurations.size());
+ for (size_t i = 0; i < arrayActualDurations.size(); i++) {
+ actualList[i].timeStampNanos = arrayTimeStamps[i];
+ actualList[i].durationNanos = arrayActualDurations[i];
+ }
+ reportActualWorkDuration(session_ptr, actualList);
+}
+
+static jlong nativeGetHintSessionPreferredRate(JNIEnv* /* env */, jclass /* clazz */) {
+ return static_cast<jlong>(getHintSessionPreferredRate());
+}
+
+// ----------------------------------------------------------------------------
+static const JNINativeMethod sHintManagerServiceMethods[] = {
+ /* name, signature, funcPtr */
+ {"nativeInit", "()V", (void*)nativeInit},
+ {"nativeCreateHintSession", "(II[IJ)J", (void*)nativeCreateHintSession},
+ {"nativePauseHintSession", "(J)V", (void*)nativePauseHintSession},
+ {"nativeResumeHintSession", "(J)V", (void*)nativeResumeHintSession},
+ {"nativeCloseHintSession", "(J)V", (void*)nativeCloseHintSession},
+ {"nativeUpdateTargetWorkDuration", "(JJ)V", (void*)nativeUpdateTargetWorkDuration},
+ {"nativeReportActualWorkDuration", "(J[J[J)V", (void*)nativeReportActualWorkDuration},
+ {"nativeGetHintSessionPreferredRate", "()J", (void*)nativeGetHintSessionPreferredRate},
+};
+
+int register_android_server_HintManagerService(JNIEnv* env) {
+ return jniRegisterNativeMethods(env,
+ "com/android/server/power/hint/"
+ "HintManagerService$NativeWrapper",
+ sHintManagerServiceMethods, NELEM(sHintManagerServiceMethods));
+}
+
+} /* namespace android */
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index 9b7e27d891c4..ae7ea3cd90e8 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -100,7 +100,7 @@ static bool setPowerMode(Mode mode, bool enabled) {
ALOGD("Excessive delay in setting interactive mode to %s while turning screen %s",
enabled ? "true" : "false", enabled ? "on" : "off");
}
- return result == power::HalResult::SUCCESSFUL;
+ return result.isOk();
}
void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType,
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 03a01523b8e2..f257686cbf3d 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -30,6 +30,7 @@ int register_android_server_InputManager(JNIEnv* env);
int register_android_server_LightsService(JNIEnv* env);
int register_android_server_PowerManagerService(JNIEnv* env);
int register_android_server_PowerStatsService(JNIEnv* env);
+int register_android_server_HintManagerService(JNIEnv* env);
int register_android_server_storage_AppFuse(JNIEnv* env);
int register_android_server_SerialService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
@@ -79,6 +80,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_broadcastradio_Tuner(vm, env);
register_android_server_PowerManagerService(env);
register_android_server_PowerStatsService(env);
+ register_android_server_HintManagerService(env);
register_android_server_SerialService(env);
register_android_server_InputManager(env);
register_android_server_LightsService(env);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1426579a0f64..47e72ba0e6fc 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -170,6 +170,7 @@ import com.android.server.policy.role.RoleServicePlatformHelperImpl;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
import com.android.server.power.ThermalManagerService;
+import com.android.server.power.hint.HintManagerService;
import com.android.server.powerstats.PowerStatsService;
import com.android.server.profcollect.ProfcollectForwardingService;
import com.android.server.recoverysystem.RecoverySystemService;
@@ -1074,6 +1075,10 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(ThermalManagerService.class);
t.traceEnd();
+ t.traceBegin("StartHintManager");
+ mSystemServiceManager.startService(HintManagerService.class);
+ t.traceEnd();
+
// Now that the power manager has been started, let the activity manager
// initialize power management features.
t.traceBegin("InitPowerManagement");
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
new file mode 100644
index 000000000000..aaf40d7e4461
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -0,0 +1,268 @@
+/*
+ * 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.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.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.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;
+
+ 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);
+ }
+
+ 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 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());
+ }
+}