diff options
author | Jimmy Shiu <jimmyshiu@google.com> | 2021-03-15 22:55:49 +0800 |
---|---|---|
committer | Wei Wang <wvw@google.com> | 2021-04-10 10:21:17 -0700 |
commit | 25318b7316a72f5fd2fa170825199f985daf29b7 (patch) | |
tree | eda1bbfbe33b7b50be0b285f8dbb365ca8da3c78 | |
parent | 4c6e5036b78e8ec92996248f9acd792641a10044 (diff) |
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>
7 files changed, 895 insertions, 4 deletions
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java new file mode 100644 index 000000000000..fc7628c28b8b --- /dev/null +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.hint; + +import android.app.ActivityManager; +import android.app.IUidObserver; +import android.content.Context; +import android.os.Binder; +import android.os.IBinder; +import android.os.IHintManager; +import android.os.IHintSession; +import android.os.Process; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.DumpUtils; +import com.android.internal.util.Preconditions; +import com.android.server.FgThread; +import com.android.server.SystemService; +import com.android.server.utils.Slogf; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Arrays; + +/** An hint service implementation that runs in System Server process. */ +public final class HintManagerService extends SystemService { + private static final String TAG = "HintManagerService"; + private static final boolean DEBUG = false; + @VisibleForTesting final long mHintSessionPreferredRate; + + @GuardedBy("mLock") + private final ArrayMap<Integer, ArrayMap<IBinder, AppHintSession>> mActiveSessions; + + /** Lock to protect HAL handles and listen list. */ + private final Object mLock = new Object(); + + @VisibleForTesting final UidObserver mUidObserver; + + private final NativeWrapper mNativeWrapper; + + @VisibleForTesting final IHintManager.Stub mService = new BinderService(); + + public HintManagerService(Context context) { + this(context, new Injector()); + } + + @VisibleForTesting + HintManagerService(Context context, Injector injector) { + super(context); + mActiveSessions = new ArrayMap<>(); + mNativeWrapper = injector.createNativeWrapper(); + mNativeWrapper.halInit(); + mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate(); + mUidObserver = new UidObserver(); + } + + @VisibleForTesting + static class Injector { + NativeWrapper createNativeWrapper() { + return new NativeWrapper(); + } + } + + private boolean isHalSupported() { + return mHintSessionPreferredRate != -1; + } + + @Override + public void onStart() { + publishBinderService(Context.PERFORMANCE_HINT_SERVICE, mService, /* allowIsolated= */ true); + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + systemReady(); + } + } + + private void systemReady() { + Slogf.v(TAG, "Initializing HintManager service..."); + try { + ActivityManager.getService().registerUidObserver(mUidObserver, + ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE, + ActivityManager.PROCESS_STATE_UNKNOWN, null); + } catch (RemoteException e) { + // ignored; both services live in system_server + } + + } + + /** + * Wrapper around the static-native methods from native. + * + * This class exists to allow us to mock static native methods in our tests. If mocking static + * methods becomes easier than this in the future, we can delete this class. + */ + @VisibleForTesting + public static class NativeWrapper { + private native void nativeInit(); + + private static native long nativeCreateHintSession(int tgid, int uid, int[] tids, + long durationNanos); + + private static native void nativePauseHintSession(long halPtr); + + private static native void nativeResumeHintSession(long halPtr); + + private static native void nativeCloseHintSession(long halPtr); + + private static native void nativeUpdateTargetWorkDuration( + long halPtr, long targetDurationNanos); + + private static native void nativeReportActualWorkDuration( + long halPtr, long[] actualDurationNanos, long[] timeStampNanos); + + private static native long nativeGetHintSessionPreferredRate(); + + /** Wrapper for HintManager.nativeInit */ + public void halInit() { + nativeInit(); + } + + /** Wrapper for HintManager.nativeCreateHintSession */ + public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) { + return nativeCreateHintSession(tgid, uid, tids, durationNanos); + } + + /** Wrapper for HintManager.nativePauseHintSession */ + public void halPauseHintSession(long halPtr) { + nativePauseHintSession(halPtr); + } + + /** Wrapper for HintManager.nativeResumeHintSession */ + public void halResumeHintSession(long halPtr) { + nativeResumeHintSession(halPtr); + } + + /** Wrapper for HintManager.nativeCloseHintSession */ + public void halCloseHintSession(long halPtr) { + nativeCloseHintSession(halPtr); + } + + /** Wrapper for HintManager.nativeUpdateTargetWorkDuration */ + public void halUpdateTargetWorkDuration(long halPtr, long targetDurationNanos) { + nativeUpdateTargetWorkDuration(halPtr, targetDurationNanos); + } + + /** Wrapper for HintManager.nativeReportActualWorkDuration */ + public void halReportActualWorkDuration( + long halPtr, long[] actualDurationNanos, long[] timeStampNanos) { + nativeReportActualWorkDuration(halPtr, actualDurationNanos, + timeStampNanos); + } + + /** Wrapper for HintManager.nativeGetHintSessionPreferredRate */ + public long halGetHintSessionPreferredRate() { + return nativeGetHintSessionPreferredRate(); + } + } + + @VisibleForTesting + final class UidObserver extends IUidObserver.Stub { + private final SparseArray<Integer> mProcStatesCache = new SparseArray<>(); + + public boolean isUidForeground(int uid) { + synchronized (mLock) { + return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) + <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; + } + } + + @Override + public void onUidGone(int uid, boolean disabled) { + FgThread.getHandler().post(() -> { + synchronized (mLock) { + ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid); + if (tokenMap == null) { + return; + } + for (int i = tokenMap.size() - 1; i >= 0; i--) { + // Will remove the session from tokenMap + tokenMap.valueAt(i).close(); + } + mProcStatesCache.delete(uid); + } + }); + } + + @Override + public void onUidActive(int uid) { + } + + @Override + public void onUidIdle(int uid, boolean disabled) { + } + + /** + * The IUidObserver callback is called from the system_server, so it'll be a direct function + * call from ActivityManagerService. Do not do heavy logic here. + */ + @Override + public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { + FgThread.getHandler().post(() -> { + synchronized (mLock) { + mProcStatesCache.put(uid, procState); + ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid); + if (tokenMap == null) { + return; + } + for (AppHintSession s : tokenMap.values()) { + s.onProcStateChanged(); + } + } + }); + } + + @Override + public void onUidCachedChanged(int uid, boolean cached) { + } + } + + @VisibleForTesting + IHintManager.Stub getBinderServiceInstance() { + return mService; + } + + private boolean checkTidValid(int tgid, int [] tids) { + // Make sure all tids belongs to the same process. + for (int threadId : tids) { + if (!Process.isThreadInProcess(tgid, threadId)) { + return false; + } + } + return true; + } + + @VisibleForTesting + final class BinderService extends IHintManager.Stub { + @Override + public IHintSession createHintSession(IBinder token, int[] tids, long durationNanos) { + if (!isHalSupported()) return null; + + java.util.Objects.requireNonNull(token); + java.util.Objects.requireNonNull(tids); + Preconditions.checkArgument(tids.length != 0, "tids should" + + " not be empty."); + + int uid = Binder.getCallingUid(); + int tid = Binder.getCallingPid(); + int pid = Process.getThreadGroupLeader(tid); + + final long identity = Binder.clearCallingIdentity(); + try { + if (!checkTidValid(pid, tids)) { + throw new SecurityException("Some tid doesn't belong to the process"); + } + + long halSessionPtr = mNativeWrapper.halCreateHintSession(pid, uid, tids, + durationNanos); + if (halSessionPtr == 0) return null; + + AppHintSession hs = new AppHintSession(uid, pid, tids, token, + halSessionPtr, durationNanos); + synchronized (mLock) { + ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(uid); + if (tokenMap == null) { + tokenMap = new ArrayMap<>(1); + mActiveSessions.put(uid, tokenMap); + } + tokenMap.put(token, hs); + return hs; + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public long getHintSessionPreferredRate() { + return mHintSessionPreferredRate; + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) { + return; + } + synchronized (mLock) { + pw.println("HintSessionPreferredRate: " + mHintSessionPreferredRate); + pw.println("HAL Support: " + isHalSupported()); + pw.println("Active Sessions:"); + for (int i = 0; i < mActiveSessions.size(); i++) { + pw.println("Uid " + mActiveSessions.keyAt(i).toString() + ":"); + ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.valueAt(i); + for (int j = 0; j < tokenMap.size(); j++) { + pw.println(" Session " + j + ":"); + tokenMap.valueAt(j).dump(pw, " "); + } + } + } + } + } + + @VisibleForTesting + final class AppHintSession extends IHintSession.Stub implements IBinder.DeathRecipient { + protected final int mUid; + protected final int mPid; + protected final int[] mThreadIds; + protected final IBinder mToken; + protected long mHalSessionPtr; + protected long mTargetDurationNanos; + protected boolean mUpdateAllowed; + + protected AppHintSession( + int uid, int pid, int[] threadIds, IBinder token, + long halSessionPtr, long durationNanos) { + mUid = uid; + mPid = pid; + mToken = token; + mThreadIds = threadIds; + mHalSessionPtr = halSessionPtr; + mTargetDurationNanos = durationNanos; + mUpdateAllowed = true; + updateHintAllowed(); + try { + token.linkToDeath(this, 0); + } catch (RemoteException e) { + mNativeWrapper.halCloseHintSession(mHalSessionPtr); + throw new IllegalStateException("Client already dead", e); + } + } + + @VisibleForTesting + boolean updateHintAllowed() { + synchronized (mLock) { + final boolean allowed = mUidObserver.isUidForeground(mUid); + if (allowed && !mUpdateAllowed) resume(); + if (!allowed && mUpdateAllowed) pause(); + mUpdateAllowed = allowed; + return mUpdateAllowed; + } + } + + @Override + public void updateTargetWorkDuration(long targetDurationNanos) { + synchronized (mLock) { + if (mHalSessionPtr == 0 || !updateHintAllowed()) { + return; + } + Preconditions.checkArgument(targetDurationNanos > 0, "Expected" + + " the target duration to be greater than 0."); + mNativeWrapper.halUpdateTargetWorkDuration(mHalSessionPtr, targetDurationNanos); + mTargetDurationNanos = targetDurationNanos; + } + } + + @Override + public void reportActualWorkDuration(long[] actualDurationNanos, long[] timeStampNanos) { + synchronized (mLock) { + if (mHalSessionPtr == 0 || !updateHintAllowed()) { + return; + } + Preconditions.checkArgument(actualDurationNanos.length != 0, "the count" + + " of hint durations shouldn't be 0."); + Preconditions.checkArgument(actualDurationNanos.length == timeStampNanos.length, + "The length of durations and timestamps should be the same."); + for (int i = 0; i < actualDurationNanos.length; i++) { + if (actualDurationNanos[i] <= 0) { + throw new IllegalArgumentException( + String.format("durations[%d]=%d should be greater than 0", + i, actualDurationNanos[i])); + } + } + mNativeWrapper.halReportActualWorkDuration(mHalSessionPtr, actualDurationNanos, + timeStampNanos); + } + } + + /** TODO: consider monitor session threads and close session if any thread is dead. */ + @Override + public void close() { + synchronized (mLock) { + if (mHalSessionPtr == 0) return; + mNativeWrapper.halCloseHintSession(mHalSessionPtr); + mHalSessionPtr = 0; + mToken.unlinkToDeath(this, 0); + ArrayMap<IBinder, AppHintSession> tokenMap = mActiveSessions.get(mUid); + if (tokenMap == null) { + Slogf.w(TAG, "UID %d is note present in active session map", mUid); + } + tokenMap.remove(mToken); + if (tokenMap.isEmpty()) mActiveSessions.remove(mUid); + } + } + + private void onProcStateChanged() { + updateHintAllowed(); + } + + private void pause() { + synchronized (mLock) { + if (mHalSessionPtr == 0) return; + mNativeWrapper.halPauseHintSession(mHalSessionPtr); + } + } + + private void resume() { + synchronized (mLock) { + if (mHalSessionPtr == 0) return; + mNativeWrapper.halResumeHintSession(mHalSessionPtr); + } + } + + private void dump(PrintWriter pw, String prefix) { + synchronized (mLock) { + pw.println(prefix + "SessionPID: " + mPid); + pw.println(prefix + "SessionUID: " + mUid); + pw.println(prefix + "SessionTIDs: " + Arrays.toString(mThreadIds)); + pw.println(prefix + "SessionTargetDurationNanos: " + mTargetDurationNanos); + pw.println(prefix + "SessionAllowed: " + updateHintAllowed()); + } + } + + @Override + public void binderDied() { + close(); + } + + } +} diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 15f57653840b..a99679ade958 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -49,6 +49,7 @@ cc_library_static { "com_android_server_net_NetworkStatsService.cpp", "com_android_server_power_PowerManagerService.cpp", "com_android_server_powerstats_PowerStatsService.cpp", + "com_android_server_hint_HintManagerService.cpp", "com_android_server_SerialService.cpp", "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp", "com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp", @@ -158,7 +159,7 @@ cc_defaults { "android.hardware.memtrack-V1-ndk_platform", "android.hardware.power@1.0", "android.hardware.power@1.1", - "android.hardware.power-V1-cpp", + "android.hardware.power-V2-cpp", "android.hardware.power.stats@1.0", "android.hardware.power.stats-V1-ndk_platform", "android.hardware.thermal@1.0", @@ -195,8 +196,8 @@ cc_defaults { "libchrome", "libmojo", ], - } - } + }, + }, } filegroup { diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp new file mode 100644 index 000000000000..000cb839002b --- /dev/null +++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define TAG "HintManagerService-JNI" + +//#define LOG_NDEBUG 0 + +#include <android-base/stringprintf.h> +#include <android/hardware/power/IPower.h> +#include <android_runtime/AndroidRuntime.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedPrimitiveArray.h> +#include <powermanager/PowerHalController.h> +#include <utils/Log.h> + +#include <unistd.h> +#include <cinttypes> + +#include <sys/types.h> + +#include "jni.h" + +using android::hardware::power::IPowerHintSession; +using android::hardware::power::WorkDuration; + +using android::base::StringPrintf; + +namespace android { + +static power::PowerHalController gPowerHalController; + +static jlong createHintSession(JNIEnv* env, int32_t tgid, int32_t uid, + std::vector<int32_t> threadIds, int64_t durationNanos) { + auto result = + gPowerHalController.createHintSession(tgid, uid, std::move(threadIds), durationNanos); + if (result.isOk()) { + sp<IPowerHintSession> appSession = result.value(); + if (appSession) appSession->incStrong(env); + return reinterpret_cast<jlong>(appSession.get()); + } + return 0; +} + +static void pauseHintSession(JNIEnv* env, int64_t session_ptr) { + sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr); + appSession->pause(); +} + +static void resumeHintSession(JNIEnv* env, int64_t session_ptr) { + sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr); + appSession->resume(); +} + +static void closeHintSession(JNIEnv* env, int64_t session_ptr) { + sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr); + appSession->close(); + appSession->decStrong(env); +} + +static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDurationNanos) { + sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr); + appSession->updateTargetWorkDuration(targetDurationNanos); +} + +static void reportActualWorkDuration(int64_t session_ptr, + const std::vector<WorkDuration>& actualDurations) { + sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr); + appSession->reportActualWorkDuration(actualDurations); +} + +static int64_t getHintSessionPreferredRate() { + int64_t rate = -1; + auto result = gPowerHalController.getHintSessionPreferredRate(); + if (result.isOk()) { + rate = result.value(); + } + return rate; +} + +// ---------------------------------------------------------------------------- +static void nativeInit(JNIEnv* env, jobject obj) { + gPowerHalController.init(); +} + +static jlong nativeCreateHintSession(JNIEnv* env, jclass /* clazz */, jint tgid, jint uid, + jintArray tids, jlong durationNanos) { + ScopedIntArrayRO tidArray(env, tids); + if (nullptr == tidArray.get() || tidArray.size() == 0) { + ALOGW("GetIntArrayElements returns nullptr."); + return 0; + } + std::vector<int32_t> threadIds(tidArray.size()); + for (size_t i = 0; i < tidArray.size(); i++) { + threadIds[i] = tidArray[i]; + } + return createHintSession(env, tgid, uid, std::move(threadIds), durationNanos); +} + +static void nativePauseHintSession(JNIEnv* env, jclass /* clazz */, jlong session_ptr) { + pauseHintSession(env, session_ptr); +} + +static void nativeResumeHintSession(JNIEnv* env, jclass /* clazz */, jlong session_ptr) { + resumeHintSession(env, session_ptr); +} + +static void nativeCloseHintSession(JNIEnv* env, jclass /* clazz */, jlong session_ptr) { + closeHintSession(env, session_ptr); +} + +static void nativeUpdateTargetWorkDuration(JNIEnv* /* env */, jclass /* clazz */, jlong session_ptr, + jlong targetDurationNanos) { + updateTargetWorkDuration(session_ptr, targetDurationNanos); +} + +static void nativeReportActualWorkDuration(JNIEnv* env, jclass /* clazz */, jlong session_ptr, + jlongArray actualDurations, jlongArray timeStamps) { + ScopedLongArrayRO arrayActualDurations(env, actualDurations); + ScopedLongArrayRO arrayTimeStamps(env, timeStamps); + + std::vector<WorkDuration> actualList(arrayActualDurations.size()); + for (size_t i = 0; i < arrayActualDurations.size(); i++) { + actualList[i].timeStampNanos = arrayTimeStamps[i]; + actualList[i].durationNanos = arrayActualDurations[i]; + } + reportActualWorkDuration(session_ptr, actualList); +} + +static jlong nativeGetHintSessionPreferredRate(JNIEnv* /* env */, jclass /* clazz */) { + return static_cast<jlong>(getHintSessionPreferredRate()); +} + +// ---------------------------------------------------------------------------- +static const JNINativeMethod sHintManagerServiceMethods[] = { + /* name, signature, funcPtr */ + {"nativeInit", "()V", (void*)nativeInit}, + {"nativeCreateHintSession", "(II[IJ)J", (void*)nativeCreateHintSession}, + {"nativePauseHintSession", "(J)V", (void*)nativePauseHintSession}, + {"nativeResumeHintSession", "(J)V", (void*)nativeResumeHintSession}, + {"nativeCloseHintSession", "(J)V", (void*)nativeCloseHintSession}, + {"nativeUpdateTargetWorkDuration", "(JJ)V", (void*)nativeUpdateTargetWorkDuration}, + {"nativeReportActualWorkDuration", "(J[J[J)V", (void*)nativeReportActualWorkDuration}, + {"nativeGetHintSessionPreferredRate", "()J", (void*)nativeGetHintSessionPreferredRate}, +}; + +int register_android_server_HintManagerService(JNIEnv* env) { + return jniRegisterNativeMethods(env, + "com/android/server/power/hint/" + "HintManagerService$NativeWrapper", + sHintManagerServiceMethods, NELEM(sHintManagerServiceMethods)); +} + +} /* namespace android */ diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp index 9b7e27d891c4..ae7ea3cd90e8 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.cpp +++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp @@ -100,7 +100,7 @@ static bool setPowerMode(Mode mode, bool enabled) { ALOGD("Excessive delay in setting interactive mode to %s while turning screen %s", enabled ? "true" : "false", enabled ? "on" : "off"); } - return result == power::HalResult::SUCCESSFUL; + return result.isOk(); } void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType, diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 03a01523b8e2..f257686cbf3d 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -30,6 +30,7 @@ int register_android_server_InputManager(JNIEnv* env); int register_android_server_LightsService(JNIEnv* env); int register_android_server_PowerManagerService(JNIEnv* env); int register_android_server_PowerStatsService(JNIEnv* env); +int register_android_server_HintManagerService(JNIEnv* env); int register_android_server_storage_AppFuse(JNIEnv* env); int register_android_server_SerialService(JNIEnv* env); int register_android_server_SystemServer(JNIEnv* env); @@ -79,6 +80,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_broadcastradio_Tuner(vm, env); register_android_server_PowerManagerService(env); register_android_server_PowerStatsService(env); + register_android_server_HintManagerService(env); register_android_server_SerialService(env); register_android_server_InputManager(env); register_android_server_LightsService(env); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 1426579a0f64..47e72ba0e6fc 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -170,6 +170,7 @@ import com.android.server.policy.role.RoleServicePlatformHelperImpl; import com.android.server.power.PowerManagerService; import com.android.server.power.ShutdownThread; import com.android.server.power.ThermalManagerService; +import com.android.server.power.hint.HintManagerService; import com.android.server.powerstats.PowerStatsService; import com.android.server.profcollect.ProfcollectForwardingService; import com.android.server.recoverysystem.RecoverySystemService; @@ -1074,6 +1075,10 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(ThermalManagerService.class); t.traceEnd(); + t.traceBegin("StartHintManager"); + mSystemServiceManager.startService(HintManagerService.class); + t.traceEnd(); + // Now that the power manager has been started, let the activity manager // initialize power management features. t.traceBegin("InitPowerManagement"); diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java new file mode 100644 index 000000000000..aaf40d7e4461 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.power.hint; + + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.content.Context; +import android.os.Binder; +import android.os.IBinder; +import android.os.IHintSession; +import android.os.Process; + +import com.android.server.FgThread; +import com.android.server.power.hint.HintManagerService.AppHintSession; +import com.android.server.power.hint.HintManagerService.Injector; +import com.android.server.power.hint.HintManagerService.NativeWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link com.android.server.power.hint.HintManagerService}. + * + * Build/Install/Run: + * atest FrameworksServicesTests:HintManagerServiceTest + */ +public class HintManagerServiceTest { + private static final long DEFAULT_HINT_PREFERRED_RATE = 16666666L; + private static final long DEFAULT_TARGET_DURATION = 16666666L; + private static final int UID = Process.myUid(); + private static final int TID = Process.myPid(); + private static final int TGID = Process.getThreadGroupLeader(TID); + private static final int[] SESSION_TIDS_A = new int[] {TID}; + private static final int[] SESSION_TIDS_B = new int[] {TID}; + private static final long[] DURATIONS_THREE = new long[] {1L, 100L, 1000L}; + private static final long[] TIMESTAMPS_THREE = new long[] {1L, 2L, 3L}; + private static final long[] DURATIONS_ZERO = new long[] {}; + private static final long[] TIMESTAMPS_ZERO = new long[] {}; + private static final long[] TIMESTAMPS_TWO = new long[] {1L, 2L}; + + @Mock private Context mContext; + @Mock private HintManagerService.NativeWrapper mNativeWrapperMock; + + private HintManagerService mService; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + when(mNativeWrapperMock.halGetHintSessionPreferredRate()) + .thenReturn(DEFAULT_HINT_PREFERRED_RATE); + when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A), + eq(DEFAULT_TARGET_DURATION))).thenReturn(1L); + when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_B), + eq(DEFAULT_TARGET_DURATION))).thenReturn(2L); + } + + private HintManagerService createService() { + mService = new HintManagerService(mContext, new Injector() { + NativeWrapper createNativeWrapper() { + return mNativeWrapperMock; + } + }); + return mService; + } + + @Test + public void testInitializeService() { + HintManagerService service = createService(); + verify(mNativeWrapperMock).halInit(); + assertThat(service.mHintSessionPreferredRate).isEqualTo(DEFAULT_HINT_PREFERRED_RATE); + } + + @Test + public void testCreateHintSession() throws Exception { + HintManagerService service = createService(); + IBinder token = new Binder(); + + IHintSession a = service.getBinderServiceInstance().createHintSession(token, + SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + assertNotNull(a); + + IHintSession b = service.getBinderServiceInstance().createHintSession(token, + SESSION_TIDS_B, DEFAULT_TARGET_DURATION); + assertNotEquals(a, b); + } + + @Test + public void testPauseResumeHintSession() throws Exception { + HintManagerService service = createService(); + IBinder token = new Binder(); + + AppHintSession a = (AppHintSession) service.getBinderServiceInstance() + .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + + // Set session to background and calling updateHintAllowed() would invoke pause(); + service.mUidObserver.onUidStateChanged( + a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + final Object sync = new Object(); + FgThread.getHandler().post(() -> { + synchronized (sync) { + sync.notify(); + } + }); + synchronized (sync) { + sync.wait(); + } + assumeFalse(a.updateHintAllowed()); + verify(mNativeWrapperMock, times(1)).halPauseHintSession(anyLong()); + + // Set session to foreground and calling updateHintAllowed() would invoke resume(); + service.mUidObserver.onUidStateChanged( + a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); + FgThread.getHandler().post(() -> { + synchronized (sync) { + sync.notify(); + } + }); + synchronized (sync) { + sync.wait(); + } + assumeTrue(a.updateHintAllowed()); + verify(mNativeWrapperMock, times(1)).halResumeHintSession(anyLong()); + } + + @Test + public void testCloseHintSession() throws Exception { + HintManagerService service = createService(); + IBinder token = new Binder(); + + IHintSession a = service.getBinderServiceInstance().createHintSession(token, + SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + + a.close(); + verify(mNativeWrapperMock, times(1)).halCloseHintSession(anyLong()); + } + + @Test + public void testUpdateTargetWorkDuration() throws Exception { + HintManagerService service = createService(); + IBinder token = new Binder(); + + IHintSession a = service.getBinderServiceInstance().createHintSession(token, + SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + + assertThrows(IllegalArgumentException.class, () -> { + a.updateTargetWorkDuration(-1L); + }); + + assertThrows(IllegalArgumentException.class, () -> { + a.updateTargetWorkDuration(0L); + }); + + a.updateTargetWorkDuration(100L); + verify(mNativeWrapperMock, times(1)).halUpdateTargetWorkDuration(anyLong(), eq(100L)); + } + + @Test + public void testReportActualWorkDuration() throws Exception { + HintManagerService service = createService(); + IBinder token = new Binder(); + + AppHintSession a = (AppHintSession) service.getBinderServiceInstance() + .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + + a.updateTargetWorkDuration(100L); + a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE); + verify(mNativeWrapperMock, times(1)).halReportActualWorkDuration(anyLong(), + eq(DURATIONS_THREE), eq(TIMESTAMPS_THREE)); + + assertThrows(IllegalArgumentException.class, () -> { + a.reportActualWorkDuration(DURATIONS_ZERO, TIMESTAMPS_THREE); + }); + + assertThrows(IllegalArgumentException.class, () -> { + a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_ZERO); + }); + + assertThrows(IllegalArgumentException.class, () -> { + a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_TWO); + }); + + reset(mNativeWrapperMock); + // Set session to background, then the duration would not be updated. + service.mUidObserver.onUidStateChanged( + a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + final Object sync = new Object(); + FgThread.getHandler().post(() -> { + synchronized (sync) { + sync.notify(); + } + }); + synchronized (sync) { + sync.wait(); + } + assumeFalse(a.updateHintAllowed()); + a.reportActualWorkDuration(DURATIONS_THREE, TIMESTAMPS_THREE); + verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any()); + } + + @Test + public void testDoHintInBackground() throws Exception { + HintManagerService service = createService(); + IBinder token = new Binder(); + + AppHintSession a = (AppHintSession) service.getBinderServiceInstance() + .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + + service.mUidObserver.onUidStateChanged( + a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); + final Object sync = new Object(); + FgThread.getHandler().post(() -> { + synchronized (sync) { + sync.notify(); + } + }); + synchronized (sync) { + sync.wait(); + } + assertFalse(a.updateHintAllowed()); + } + + @Test + public void testDoHintInForeground() throws Exception { + HintManagerService service = createService(); + IBinder token = new Binder(); + + AppHintSession a = (AppHintSession) service.getBinderServiceInstance() + .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION); + + service.mUidObserver.onUidStateChanged( + a.mUid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); + assertTrue(a.updateHintAllowed()); + } +} |