ADPF: Add HintManagerService
Test: Manual test, run bouncy ball Test: atest HintManagerServiceTest Test: adb shell dumpsys hint Bug: 158791282 Change-Id: I50b19ab7629f006decbcddd653fb67588fc4160b Signed-off-by: Wei Wang <wvw@google.com>
This commit is contained in:
@@ -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_power_PowerManagerService.cpp",
|
||||
"com_android_server_powerstats_PowerStatsService.cpp",
|
||||
"com_android_server_hint_HintManagerService.cpp",
|
||||
"com_android_server_SerialService.cpp",
|
||||
"com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
|
||||
"com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp",
|
||||
@@ -158,7 +159,7 @@ cc_defaults {
|
||||
"android.hardware.memtrack-V1-ndk_platform",
|
||||
"android.hardware.power@1.0",
|
||||
"android.hardware.power@1.1",
|
||||
"android.hardware.power-V1-cpp",
|
||||
"android.hardware.power-V2-cpp",
|
||||
"android.hardware.power.stats@1.0",
|
||||
"android.hardware.power.stats-V1-ndk_platform",
|
||||
"android.hardware.thermal@1.0",
|
||||
@@ -195,8 +196,8 @@ cc_defaults {
|
||||
"libchrome",
|
||||
"libmojo",
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
filegroup {
|
||||
|
||||
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",
|
||||
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,
|
||||
|
||||
@@ -30,6 +30,7 @@ int register_android_server_InputManager(JNIEnv* env);
|
||||
int register_android_server_LightsService(JNIEnv* env);
|
||||
int register_android_server_PowerManagerService(JNIEnv* env);
|
||||
int register_android_server_PowerStatsService(JNIEnv* env);
|
||||
int register_android_server_HintManagerService(JNIEnv* env);
|
||||
int register_android_server_storage_AppFuse(JNIEnv* env);
|
||||
int register_android_server_SerialService(JNIEnv* env);
|
||||
int register_android_server_SystemServer(JNIEnv* env);
|
||||
@@ -79,6 +80,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
|
||||
register_android_server_broadcastradio_Tuner(vm, env);
|
||||
register_android_server_PowerManagerService(env);
|
||||
register_android_server_PowerStatsService(env);
|
||||
register_android_server_HintManagerService(env);
|
||||
register_android_server_SerialService(env);
|
||||
register_android_server_InputManager(env);
|
||||
register_android_server_LightsService(env);
|
||||
|
||||
@@ -170,6 +170,7 @@ import com.android.server.policy.role.RoleServicePlatformHelperImpl;
|
||||
import com.android.server.power.PowerManagerService;
|
||||
import com.android.server.power.ShutdownThread;
|
||||
import com.android.server.power.ThermalManagerService;
|
||||
import com.android.server.power.hint.HintManagerService;
|
||||
import com.android.server.powerstats.PowerStatsService;
|
||||
import com.android.server.profcollect.ProfcollectForwardingService;
|
||||
import com.android.server.recoverysystem.RecoverySystemService;
|
||||
@@ -1074,6 +1075,10 @@ public final class SystemServer implements Dumpable {
|
||||
mSystemServiceManager.startService(ThermalManagerService.class);
|
||||
t.traceEnd();
|
||||
|
||||
t.traceBegin("StartHintManager");
|
||||
mSystemServiceManager.startService(HintManagerService.class);
|
||||
t.traceEnd();
|
||||
|
||||
// Now that the power manager has been started, let the activity manager
|
||||
// initialize power management features.
|
||||
t.traceBegin("InitPowerManagement");
|
||||
|
||||
@@ -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