Merge changes from topic "android_dynamic_performance_framework" into sc-dev
* changes: Integrate HWUI with PerformanceHintManager ADPF: Add HintManagerService ADPF: PerformanceHintManager API implementation
This commit is contained in:
@@ -10644,6 +10644,7 @@ package android.content {
|
|||||||
field public static final String NOTIFICATION_SERVICE = "notification";
|
field public static final String NOTIFICATION_SERVICE = "notification";
|
||||||
field public static final String NSD_SERVICE = "servicediscovery";
|
field public static final String NSD_SERVICE = "servicediscovery";
|
||||||
field public static final String PEOPLE_SERVICE = "people";
|
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 POWER_SERVICE = "power";
|
||||||
field public static final String PRINT_SERVICE = "print";
|
field public static final String PRINT_SERVICE = "print";
|
||||||
field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1
|
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
|
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 {
|
public final class PersistableBundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
|
||||||
ctor public PersistableBundle();
|
ctor public PersistableBundle();
|
||||||
ctor public PersistableBundle(int);
|
ctor public PersistableBundle(int);
|
||||||
|
|||||||
@@ -158,12 +158,14 @@ import android.os.IBatteryPropertiesRegistrar;
|
|||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.IDumpstate;
|
import android.os.IDumpstate;
|
||||||
import android.os.IHardwarePropertiesManager;
|
import android.os.IHardwarePropertiesManager;
|
||||||
|
import android.os.IHintManager;
|
||||||
import android.os.IPowerManager;
|
import android.os.IPowerManager;
|
||||||
import android.os.IRecoverySystem;
|
import android.os.IRecoverySystem;
|
||||||
import android.os.ISystemUpdateManager;
|
import android.os.ISystemUpdateManager;
|
||||||
import android.os.IThermalService;
|
import android.os.IThermalService;
|
||||||
import android.os.IUserManager;
|
import android.os.IUserManager;
|
||||||
import android.os.IncidentManager;
|
import android.os.IncidentManager;
|
||||||
|
import android.os.PerformanceHintManager;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.RecoverySystem;
|
import android.os.RecoverySystem;
|
||||||
import android.os.ServiceManager;
|
import android.os.ServiceManager;
|
||||||
@@ -592,6 +594,17 @@ public final class SystemServiceRegistry {
|
|||||||
ctx.mMainThread.getHandler());
|
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,
|
registerService(Context.RECOVERY_SERVICE, RecoverySystem.class,
|
||||||
new CachedServiceFetcher<RecoverySystem>() {
|
new CachedServiceFetcher<RecoverySystem>() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -5224,6 +5224,14 @@ public abstract class Context {
|
|||||||
*/
|
*/
|
||||||
public static final String THERMAL_SERVICE = "thermalservice";
|
public static final String THERMAL_SERVICE = "thermalservice";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Use with {@link #getSystemService(String)} to retrieve a
|
||||||
* {@link android.content.pm.ShortcutManager} for accessing the launcher shortcut service.
|
* {@link android.content.pm.ShortcutManager} for accessing the launcher shortcut service.
|
||||||
|
|||||||
33
core/java/android/os/IHintManager.aidl
Normal file
33
core/java/android/os/IHintManager.aidl
Normal file
@@ -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();
|
||||||
|
}
|
||||||
25
core/java/android/os/IHintSession.aidl
Normal file
25
core/java/android/os/IHintSession.aidl
Normal file
@@ -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();
|
||||||
|
}
|
||||||
226
core/java/android/os/PerformanceHintManager.java
Normal file
226
core/java/android/os/PerformanceHintManager.java
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -737,7 +737,7 @@ public final class Choreographer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
|
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
|
||||||
vsyncEventData.frameDeadline);
|
vsyncEventData.frameDeadline, startNanos);
|
||||||
mFrameScheduled = false;
|
mFrameScheduled = false;
|
||||||
mLastFrameTimeNanos = frameTimeNanos;
|
mLastFrameTimeNanos = frameTimeNanos;
|
||||||
mLastVsyncEventData = vsyncEventData;
|
mLastVsyncEventData = vsyncEventData;
|
||||||
|
|||||||
@@ -242,18 +242,19 @@ public final class FrameMetrics {
|
|||||||
int PERFORM_TRAVERSALS_START = 7;
|
int PERFORM_TRAVERSALS_START = 7;
|
||||||
int DRAW_START = 8;
|
int DRAW_START = 8;
|
||||||
int FRAME_DEADLINE = 9;
|
int FRAME_DEADLINE = 9;
|
||||||
int SYNC_QUEUED = 10;
|
int FRAME_START_TIME = 10;
|
||||||
int SYNC_START = 11;
|
int SYNC_QUEUED = 11;
|
||||||
int ISSUE_DRAW_COMMANDS_START = 12;
|
int SYNC_START = 12;
|
||||||
int SWAP_BUFFERS = 13;
|
int ISSUE_DRAW_COMMANDS_START = 13;
|
||||||
int FRAME_COMPLETED = 14;
|
int SWAP_BUFFERS = 14;
|
||||||
int DEQUEUE_BUFFER_DURATION = 15;
|
int FRAME_COMPLETED = 15;
|
||||||
int QUEUE_BUFFER_DURATION = 16;
|
int DEQUEUE_BUFFER_DURATION = 16;
|
||||||
int GPU_COMPLETED = 17;
|
int QUEUE_BUFFER_DURATION = 17;
|
||||||
int SWAP_BUFFERS_COMPLETED = 18;
|
int GPU_COMPLETED = 18;
|
||||||
int DISPLAY_PRESENT_TIME = 19;
|
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
|
// FrameInfoIndex::NumIndexes in libs/hwui/FrameInfo.h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,18 +87,22 @@ public final class FrameInfo {
|
|||||||
// When the frame needs to be ready by
|
// When the frame needs to be ready by
|
||||||
public static final int FRAME_DEADLINE = 9;
|
public static final int FRAME_DEADLINE = 9;
|
||||||
|
|
||||||
|
// When frame actually started.
|
||||||
|
public static final int FRAME_START_TIME = 10;
|
||||||
|
|
||||||
// Must be the last one
|
// Must be the last one
|
||||||
// This value must be in sync with `UI_THREAD_FRAME_INFO_SIZE` in FrameInfo.h
|
// 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 */
|
/** checkstyle */
|
||||||
public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId,
|
public void setVsync(long intendedVsync, long usedVsync, long frameTimelineVsyncId,
|
||||||
long frameDeadline) {
|
long frameDeadline, long frameStartTime) {
|
||||||
frameInfo[FRAME_TIMELINE_VSYNC_ID] = frameTimelineVsyncId;
|
frameInfo[FRAME_TIMELINE_VSYNC_ID] = frameTimelineVsyncId;
|
||||||
frameInfo[INTENDED_VSYNC] = intendedVsync;
|
frameInfo[INTENDED_VSYNC] = intendedVsync;
|
||||||
frameInfo[VSYNC] = usedVsync;
|
frameInfo[VSYNC] = usedVsync;
|
||||||
frameInfo[FLAGS] = 0;
|
frameInfo[FLAGS] = 0;
|
||||||
frameInfo[FRAME_DEADLINE] = frameDeadline;
|
frameInfo[FRAME_DEADLINE] = frameDeadline;
|
||||||
|
frameInfo[FRAME_START_TIME] = frameStartTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** checkstyle */
|
/** checkstyle */
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import android.content.res.Configuration;
|
|||||||
import android.hardware.display.DisplayManager;
|
import android.hardware.display.DisplayManager;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.os.PerformanceHintManager;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.ServiceManager;
|
import android.os.ServiceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -165,7 +166,7 @@ public class HardwareRenderer {
|
|||||||
* to opaque with no light source configured.
|
* to opaque with no light source configured.
|
||||||
*/
|
*/
|
||||||
public HardwareRenderer() {
|
public HardwareRenderer() {
|
||||||
ProcessInitializer.sInstance.initDisplayInfo();
|
ProcessInitializer.sInstance.initUsingContext();
|
||||||
mRootNode = RenderNode.adopt(nCreateRootRenderNode());
|
mRootNode = RenderNode.adopt(nCreateRootRenderNode());
|
||||||
mRootNode.setClipToBounds(false);
|
mRootNode.setClipToBounds(false);
|
||||||
mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode);
|
mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode);
|
||||||
@@ -365,7 +366,8 @@ public class HardwareRenderer {
|
|||||||
*/
|
*/
|
||||||
public @NonNull FrameRenderRequest setVsyncTime(long vsyncTime) {
|
public @NonNull FrameRenderRequest setVsyncTime(long vsyncTime) {
|
||||||
// TODO(b/168552873): populate vsync Id once available to Choreographer public API
|
// 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);
|
mFrameInfo.addFlags(FrameInfo.FLAG_SURFACE_CANVAS);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -835,6 +837,36 @@ public class HardwareRenderer {
|
|||||||
callback.onPictureCaptured(picture);
|
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.
|
* Interface used to receive callbacks when a frame is being drawn.
|
||||||
*
|
*
|
||||||
@@ -1071,6 +1103,7 @@ public class HardwareRenderer {
|
|||||||
private boolean mIsolated = false;
|
private boolean mIsolated = false;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private String mPackageName;
|
private String mPackageName;
|
||||||
|
private PerformanceHintManager mPerformanceHintManager;
|
||||||
private IGraphicsStats mGraphicsStatsService;
|
private IGraphicsStats mGraphicsStatsService;
|
||||||
private IGraphicsStatsCallback mGraphicsStatsCallback = new IGraphicsStatsCallback.Stub() {
|
private IGraphicsStatsCallback mGraphicsStatsCallback = new IGraphicsStatsCallback.Stub() {
|
||||||
@Override
|
@Override
|
||||||
@@ -1082,6 +1115,10 @@ public class HardwareRenderer {
|
|||||||
private ProcessInitializer() {
|
private ProcessInitializer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized PerformanceHintManager getHintManager() {
|
||||||
|
return mPerformanceHintManager;
|
||||||
|
}
|
||||||
|
|
||||||
synchronized void setPackageName(String name) {
|
synchronized void setPackageName(String name) {
|
||||||
if (mInitialized) return;
|
if (mInitialized) return;
|
||||||
mPackageName = name;
|
mPackageName = name;
|
||||||
@@ -1127,15 +1164,23 @@ public class HardwareRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void initDisplayInfo() {
|
synchronized void initUsingContext() {
|
||||||
if (mDisplayInitialized) return;
|
|
||||||
if (mContext == null) return;
|
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) {
|
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;
|
mDisplayInitialized = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1167,11 +1212,14 @@ public class HardwareRenderer {
|
|||||||
display.getRefreshRate(), wideColorDataspace.mNativeDataspace,
|
display.getRefreshRate(), wideColorDataspace.mNativeDataspace,
|
||||||
display.getAppVsyncOffsetNanos(), display.getPresentationDeadlineNanos());
|
display.getAppVsyncOffsetNanos(), display.getPresentationDeadlineNanos());
|
||||||
|
|
||||||
// Defensively clear out the context
|
|
||||||
mContext = null;
|
|
||||||
mDisplayInitialized = true;
|
mDisplayInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initHintSession() {
|
||||||
|
if (mContext == null) return;
|
||||||
|
mPerformanceHintManager = mContext.getSystemService(PerformanceHintManager.class);
|
||||||
|
}
|
||||||
|
|
||||||
private void rotateBuffer() {
|
private void rotateBuffer() {
|
||||||
nRotateProcessStatsBuffer();
|
nRotateProcessStatsBuffer();
|
||||||
requestBuffer();
|
requestBuffer();
|
||||||
|
|||||||
@@ -21,29 +21,16 @@ namespace android {
|
|||||||
namespace uirenderer {
|
namespace uirenderer {
|
||||||
|
|
||||||
const std::array FrameInfoNames{
|
const std::array FrameInfoNames{
|
||||||
"Flags",
|
"Flags", "FrameTimelineVsyncId", "IntendedVsync",
|
||||||
"FrameTimelineVsyncId",
|
"Vsync", "InputEventId", "HandleInputStart",
|
||||||
"IntendedVsync",
|
"AnimationStart", "PerformTraversalsStart", "DrawStart",
|
||||||
"Vsync",
|
"FrameDeadline", "FrameStartTime", "SyncQueued",
|
||||||
"InputEventId",
|
"SyncStart", "IssueDrawCommandsStart", "SwapBuffers",
|
||||||
"HandleInputStart",
|
"FrameCompleted", "DequeueBufferDuration", "QueueBufferDuration",
|
||||||
"AnimationStart",
|
"GpuCompleted", "SwapBuffersCompleted", "DisplayPresentTime",
|
||||||
"PerformTraversalsStart",
|
|
||||||
"DrawStart",
|
|
||||||
"FrameDeadline",
|
|
||||||
"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)");
|
"Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
|
||||||
|
|
||||||
void FrameInfo::importUiThreadInfo(int64_t* info) {
|
void FrameInfo::importUiThreadInfo(int64_t* info) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
namespace android {
|
namespace android {
|
||||||
namespace uirenderer {
|
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 {
|
enum class FrameInfoIndex {
|
||||||
Flags = 0,
|
Flags = 0,
|
||||||
@@ -41,6 +41,7 @@ enum class FrameInfoIndex {
|
|||||||
PerformTraversalsStart,
|
PerformTraversalsStart,
|
||||||
DrawStart,
|
DrawStart,
|
||||||
FrameDeadline,
|
FrameDeadline,
|
||||||
|
FrameStartTime,
|
||||||
// End of UI frame info
|
// End of UI frame info
|
||||||
|
|
||||||
SyncQueued,
|
SyncQueued,
|
||||||
|
|||||||
@@ -81,6 +81,9 @@ bool Properties::isolatedProcess = false;
|
|||||||
int Properties::contextPriority = 0;
|
int Properties::contextPriority = 0;
|
||||||
float Properties::defaultSdrWhitePoint = 200.f;
|
float Properties::defaultSdrWhitePoint = 200.f;
|
||||||
|
|
||||||
|
bool Properties::useHintManager = true;
|
||||||
|
int Properties::targetCpuTimePercentage = 70;
|
||||||
|
|
||||||
bool Properties::load() {
|
bool Properties::load() {
|
||||||
bool prevDebugLayersUpdates = debugLayersUpdates;
|
bool prevDebugLayersUpdates = debugLayersUpdates;
|
||||||
bool prevDebugOverdraw = debugOverdraw;
|
bool prevDebugOverdraw = debugOverdraw;
|
||||||
@@ -128,6 +131,10 @@ bool Properties::load() {
|
|||||||
|
|
||||||
runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false);
|
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);
|
return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,20 @@ enum DebugLevel {
|
|||||||
*/
|
*/
|
||||||
#define PROPERTY_CAPTURE_SKP_FILENAME "debug.hwui.skp_filename"
|
#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.
|
* Property for whether this is running in the emulator.
|
||||||
*/
|
*/
|
||||||
@@ -253,6 +267,9 @@ public:
|
|||||||
|
|
||||||
static float defaultSdrWhitePoint;
|
static float defaultSdrWhitePoint;
|
||||||
|
|
||||||
|
static bool useHintManager;
|
||||||
|
static int targetCpuTimePercentage;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static ProfileType sProfileType;
|
static ProfileType sProfileType;
|
||||||
static bool sDisableProfileBars;
|
static bool sDisableProfileBars;
|
||||||
|
|||||||
@@ -34,14 +34,18 @@
|
|||||||
#include <renderthread/RenderProxy.h>
|
#include <renderthread/RenderProxy.h>
|
||||||
#include <renderthread/RenderTask.h>
|
#include <renderthread/RenderTask.h>
|
||||||
#include <renderthread/RenderThread.h>
|
#include <renderthread/RenderThread.h>
|
||||||
|
#include <thread/CommonPool.h>
|
||||||
#include <utils/Color.h>
|
#include <utils/Color.h>
|
||||||
#include <utils/RefBase.h>
|
#include <utils/RefBase.h>
|
||||||
#include <utils/StrongPointer.h>
|
#include <utils/StrongPointer.h>
|
||||||
#include <utils/Timers.h>
|
#include <utils/Timers.h>
|
||||||
#include <utils/TraceUtils.h>
|
#include <utils/TraceUtils.h>
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "android_graphics_HardwareRendererObserver.h"
|
#include "android_graphics_HardwareRendererObserver.h"
|
||||||
|
|
||||||
@@ -53,6 +57,10 @@ using namespace android::uirenderer::renderthread;
|
|||||||
struct {
|
struct {
|
||||||
jclass clazz;
|
jclass clazz;
|
||||||
jmethodID invokePictureCapturedCallback;
|
jmethodID invokePictureCapturedCallback;
|
||||||
|
jmethodID createHintSession;
|
||||||
|
jmethodID updateTargetWorkDuration;
|
||||||
|
jmethodID reportActualWorkDuration;
|
||||||
|
jmethodID closeHintSession;
|
||||||
} gHardwareRenderer;
|
} gHardwareRenderer;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
@@ -71,6 +79,14 @@ static JNIEnv* getenv(JavaVM* vm) {
|
|||||||
return env;
|
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);
|
typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface);
|
||||||
ANW_fromSurface fromSurface;
|
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) {
|
static void android_view_ThreadedRenderer_rotateProcessStatsBuffer(JNIEnv* env, jobject clazz) {
|
||||||
RenderProxy::rotateProcessStatsBuffer();
|
RenderProxy::rotateProcessStatsBuffer();
|
||||||
}
|
}
|
||||||
@@ -147,6 +224,12 @@ static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject claz
|
|||||||
RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr);
|
RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr);
|
||||||
ContextFactoryImpl factory(rootRenderNode);
|
ContextFactoryImpl factory(rootRenderNode);
|
||||||
RenderProxy* proxy = new RenderProxy(translucent, rootRenderNode, &factory);
|
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;
|
return (jlong) proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -769,6 +852,18 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) {
|
|||||||
gHardwareRenderer.invokePictureCapturedCallback = GetStaticMethodIDOrDie(env, hardwareRenderer,
|
gHardwareRenderer.invokePictureCapturedCallback = GetStaticMethodIDOrDie(env, hardwareRenderer,
|
||||||
"invokePictureCapturedCallback",
|
"invokePictureCapturedCallback",
|
||||||
"(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V");
|
"(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,
|
jclass frameCallbackClass = FindClassOrDie(env,
|
||||||
"android/graphics/HardwareRenderer$FrameDrawingCallback");
|
"android/graphics/HardwareRenderer$FrameDrawingCallback");
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include "../DeferredLayerUpdater.h"
|
#include "../DeferredLayerUpdater.h"
|
||||||
#include "../DisplayList.h"
|
#include "../DisplayList.h"
|
||||||
|
#include "../Properties.h"
|
||||||
#include "../RenderNode.h"
|
#include "../RenderNode.h"
|
||||||
#include "CanvasContext.h"
|
#include "CanvasContext.h"
|
||||||
#include "RenderThread.h"
|
#include "RenderThread.h"
|
||||||
@@ -44,6 +45,12 @@ void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context,
|
|||||||
mTargetNode = targetNode;
|
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) {
|
void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) {
|
||||||
LOG_ALWAYS_FATAL_IF(!mContext,
|
LOG_ALWAYS_FATAL_IF(!mContext,
|
||||||
"Lifecycle violation, there's no context to pushLayerUpdate with!");
|
"Lifecycle violation, there's no context to pushLayerUpdate with!");
|
||||||
@@ -102,6 +109,9 @@ void DrawFrameTask::run() {
|
|||||||
CanvasContext* context = mContext;
|
CanvasContext* context = mContext;
|
||||||
std::function<void(int64_t)> callback = std::move(mFrameCallback);
|
std::function<void(int64_t)> callback = std::move(mFrameCallback);
|
||||||
mFrameCallback = nullptr;
|
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*
|
// From this point on anything in "this" is *UNSAFE TO ACCESS*
|
||||||
if (canUnblockUiThread) {
|
if (canUnblockUiThread) {
|
||||||
@@ -124,6 +134,25 @@ void DrawFrameTask::run() {
|
|||||||
if (!canUnblockUiThread) {
|
if (!canUnblockUiThread) {
|
||||||
unblockUiThread();
|
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) {
|
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ public:
|
|||||||
virtual ~DrawFrameTask();
|
virtual ~DrawFrameTask();
|
||||||
|
|
||||||
void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode);
|
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) {
|
void setContentDrawBounds(int left, int top, int right, int bottom) {
|
||||||
mContentDrawBounds.set(left, top, right, bottom);
|
mContentDrawBounds.set(left, top, right, bottom);
|
||||||
}
|
}
|
||||||
@@ -107,6 +109,10 @@ private:
|
|||||||
|
|
||||||
std::function<void(int64_t)> mFrameCallback;
|
std::function<void(int64_t)> mFrameCallback;
|
||||||
std::function<void(int64_t)> mFrameCompleteCallback;
|
std::function<void(int64_t)> mFrameCompleteCallback;
|
||||||
|
|
||||||
|
nsecs_t mLastTargetWorkDuration = 0;
|
||||||
|
std::function<void(int64_t)> mUpdateTargetWorkDuration;
|
||||||
|
std::function<void(int64_t)> mReportActualWorkDuration;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace renderthread */
|
} /* namespace renderthread */
|
||||||
|
|||||||
@@ -76,6 +76,12 @@ void RenderProxy::setName(const char* name) {
|
|||||||
mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(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) {
|
void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
|
||||||
if (window) { ANativeWindow_acquire(window); }
|
if (window) { ANativeWindow_acquire(window); }
|
||||||
mRenderThread.queue().post([this, win = window, enableTimeout]() mutable {
|
mRenderThread.queue().post([this, win = window, enableTimeout]() mutable {
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ public:
|
|||||||
void setSwapBehavior(SwapBehavior swapBehavior);
|
void setSwapBehavior(SwapBehavior swapBehavior);
|
||||||
bool loadSystemProperties();
|
bool loadSystemProperties();
|
||||||
void setName(const char* name);
|
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 setSurface(ANativeWindow* window, bool enableTimeout = true);
|
||||||
void setSurfaceControl(ASurfaceControl* surfaceControl);
|
void setSurfaceControl(ASurfaceControl* surfaceControl);
|
||||||
|
|||||||
@@ -29,14 +29,23 @@ CommonPool::CommonPool() {
|
|||||||
ATRACE_CALL();
|
ATRACE_CALL();
|
||||||
|
|
||||||
CommonPool* pool = this;
|
CommonPool* pool = this;
|
||||||
|
std::mutex mLock;
|
||||||
|
std::vector<int> tids(THREAD_COUNT);
|
||||||
|
std::vector<std::condition_variable> tidConditionVars(THREAD_COUNT);
|
||||||
|
|
||||||
// Create 2 workers
|
// Create 2 workers
|
||||||
for (int i = 0; i < THREAD_COUNT; i++) {
|
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"};
|
std::array<char, 20> name{"hwuiTask"};
|
||||||
snprintf(name.data(), name.size(), "hwuiTask%d", i);
|
snprintf(name.data(), name.size(), "hwuiTask%d", i);
|
||||||
auto self = pthread_self();
|
auto self = pthread_self();
|
||||||
pthread_setname_np(self, name.data());
|
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);
|
setpriority(PRIO_PROCESS, 0, PRIORITY_FOREGROUND);
|
||||||
auto startHook = renderthread::RenderThread::getOnStartHook();
|
auto startHook = renderthread::RenderThread::getOnStartHook();
|
||||||
if (startHook) {
|
if (startHook) {
|
||||||
@@ -47,6 +56,15 @@ CommonPool::CommonPool() {
|
|||||||
});
|
});
|
||||||
worker.detach();
|
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() {
|
CommonPool& CommonPool::instance() {
|
||||||
@@ -58,6 +76,10 @@ void CommonPool::post(Task&& task) {
|
|||||||
instance().enqueue(std::move(task));
|
instance().enqueue(std::move(task));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<int> CommonPool::getThreadIds() {
|
||||||
|
return instance().mWorkerThreadIds;
|
||||||
|
}
|
||||||
|
|
||||||
void CommonPool::enqueue(Task&& task) {
|
void CommonPool::enqueue(Task&& task) {
|
||||||
std::unique_lock lock(mLock);
|
std::unique_lock lock(mLock);
|
||||||
while (!mWorkQueue.hasSpace()) {
|
while (!mWorkQueue.hasSpace()) {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <future>
|
#include <future>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace android {
|
namespace android {
|
||||||
namespace uirenderer {
|
namespace uirenderer {
|
||||||
@@ -97,6 +98,8 @@ public:
|
|||||||
return task.get_future().get();
|
return task.get_future().get();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static std::vector<int> getThreadIds();
|
||||||
|
|
||||||
// For testing purposes only, blocks until all worker threads are parked.
|
// For testing purposes only, blocks until all worker threads are parked.
|
||||||
static void waitForIdle();
|
static void waitForIdle();
|
||||||
|
|
||||||
@@ -111,6 +114,8 @@ private:
|
|||||||
|
|
||||||
void workerLoop();
|
void workerLoop();
|
||||||
|
|
||||||
|
std::vector<int> mWorkerThreadIds;
|
||||||
|
|
||||||
std::mutex mLock;
|
std::mutex mLock;
|
||||||
std::condition_variable mCondition;
|
std::condition_variable mCondition;
|
||||||
int mWaitingThreads = 0;
|
int mWaitingThreads = 0;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,7 @@ cc_library_static {
|
|||||||
"com_android_server_net_NetworkStatsService.cpp",
|
"com_android_server_net_NetworkStatsService.cpp",
|
||||||
"com_android_server_power_PowerManagerService.cpp",
|
"com_android_server_power_PowerManagerService.cpp",
|
||||||
"com_android_server_powerstats_PowerStatsService.cpp",
|
"com_android_server_powerstats_PowerStatsService.cpp",
|
||||||
|
"com_android_server_hint_HintManagerService.cpp",
|
||||||
"com_android_server_SerialService.cpp",
|
"com_android_server_SerialService.cpp",
|
||||||
"com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
|
"com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
|
||||||
"com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp",
|
"com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp",
|
||||||
@@ -158,7 +159,7 @@ cc_defaults {
|
|||||||
"android.hardware.memtrack-V1-ndk_platform",
|
"android.hardware.memtrack-V1-ndk_platform",
|
||||||
"android.hardware.power@1.0",
|
"android.hardware.power@1.0",
|
||||||
"android.hardware.power@1.1",
|
"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@1.0",
|
||||||
"android.hardware.power.stats-V1-ndk_platform",
|
"android.hardware.power.stats-V1-ndk_platform",
|
||||||
"android.hardware.thermal@1.0",
|
"android.hardware.thermal@1.0",
|
||||||
@@ -195,8 +196,8 @@ cc_defaults {
|
|||||||
"libchrome",
|
"libchrome",
|
||||||
"libmojo",
|
"libmojo",
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
filegroup {
|
filegroup {
|
||||||
|
|||||||
166
services/core/jni/com_android_server_hint_HintManagerService.cpp
Normal file
166
services/core/jni/com_android_server_hint_HintManagerService.cpp
Normal file
@@ -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 */
|
||||||
@@ -100,7 +100,7 @@ static bool setPowerMode(Mode mode, bool enabled) {
|
|||||||
ALOGD("Excessive delay in setting interactive mode to %s while turning screen %s",
|
ALOGD("Excessive delay in setting interactive mode to %s while turning screen %s",
|
||||||
enabled ? "true" : "false", enabled ? "on" : "off");
|
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,
|
void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ int register_android_server_InputManager(JNIEnv* env);
|
|||||||
int register_android_server_LightsService(JNIEnv* env);
|
int register_android_server_LightsService(JNIEnv* env);
|
||||||
int register_android_server_PowerManagerService(JNIEnv* env);
|
int register_android_server_PowerManagerService(JNIEnv* env);
|
||||||
int register_android_server_PowerStatsService(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_storage_AppFuse(JNIEnv* env);
|
||||||
int register_android_server_SerialService(JNIEnv* env);
|
int register_android_server_SerialService(JNIEnv* env);
|
||||||
int register_android_server_SystemServer(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_broadcastradio_Tuner(vm, env);
|
||||||
register_android_server_PowerManagerService(env);
|
register_android_server_PowerManagerService(env);
|
||||||
register_android_server_PowerStatsService(env);
|
register_android_server_PowerStatsService(env);
|
||||||
|
register_android_server_HintManagerService(env);
|
||||||
register_android_server_SerialService(env);
|
register_android_server_SerialService(env);
|
||||||
register_android_server_InputManager(env);
|
register_android_server_InputManager(env);
|
||||||
register_android_server_LightsService(env);
|
register_android_server_LightsService(env);
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ import com.android.server.policy.role.RoleServicePlatformHelperImpl;
|
|||||||
import com.android.server.power.PowerManagerService;
|
import com.android.server.power.PowerManagerService;
|
||||||
import com.android.server.power.ShutdownThread;
|
import com.android.server.power.ShutdownThread;
|
||||||
import com.android.server.power.ThermalManagerService;
|
import com.android.server.power.ThermalManagerService;
|
||||||
|
import com.android.server.power.hint.HintManagerService;
|
||||||
import com.android.server.powerstats.PowerStatsService;
|
import com.android.server.powerstats.PowerStatsService;
|
||||||
import com.android.server.profcollect.ProfcollectForwardingService;
|
import com.android.server.profcollect.ProfcollectForwardingService;
|
||||||
import com.android.server.recoverysystem.RecoverySystemService;
|
import com.android.server.recoverysystem.RecoverySystemService;
|
||||||
@@ -1074,6 +1075,10 @@ public final class SystemServer implements Dumpable {
|
|||||||
mSystemServiceManager.startService(ThermalManagerService.class);
|
mSystemServiceManager.startService(ThermalManagerService.class);
|
||||||
t.traceEnd();
|
t.traceEnd();
|
||||||
|
|
||||||
|
t.traceBegin("StartHintManager");
|
||||||
|
mSystemServiceManager.startService(HintManagerService.class);
|
||||||
|
t.traceEnd();
|
||||||
|
|
||||||
// Now that the power manager has been started, let the activity manager
|
// Now that the power manager has been started, let the activity manager
|
||||||
// initialize power management features.
|
// initialize power management features.
|
||||||
t.traceBegin("InitPowerManagement");
|
t.traceBegin("InitPowerManagement");
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user