Skip to content
Snippets Groups Projects
Commit 7f89ab37 authored by Matt Buckley's avatar Matt Buckley
Browse files

Add plumbing for ADPF FMQ

This patch adds plumbing for ADPF to work with FMQ, including new
getSessionChannel and closeSessionChannel methods in HintManagerService.
It also introduces a system to cache FMQ descriptors per app and track
binder tokens provided by getSessionChannel, to close the FMQ channels
when the clients die.

Bug: 315894228
Test: atest HintManagerServiceTest
Change-Id: I74a8f9d679eae4c04894cdde3f2538825ec8675b
parent 7e0de42c
No related branches found
No related tags found
No related merge requests found
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
package android.os; package android.os;
import android.os.IHintSession; import android.os.IHintSession;
import android.hardware.power.ChannelConfig;
import android.hardware.power.SessionConfig; import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag; import android.hardware.power.SessionTag;
...@@ -27,6 +28,9 @@ interface IHintManager { ...@@ -27,6 +28,9 @@ interface IHintManager {
* Creates a {@link Session} for the given set of threads and associates to a binder token. * Creates a {@link Session} for the given set of threads and associates to a binder token.
* Returns a config if creation is not supported, and HMS had to use the * Returns a config if creation is not supported, and HMS had to use the
* legacy creation method. * legacy creation method.
*
* Throws UnsupportedOperationException if ADPF is not supported, and IllegalStateException
* if creation is supported but fails.
*/ */
IHintSession createHintSessionWithConfig(in IBinder token, in int[] threadIds, IHintSession createHintSessionWithConfig(in IBinder token, in int[] threadIds,
in long durationNanos, in SessionTag tag, out @nullable SessionConfig config); in long durationNanos, in SessionTag tag, out @nullable SessionConfig config);
...@@ -38,4 +42,12 @@ interface IHintManager { ...@@ -38,4 +42,12 @@ interface IHintManager {
void setHintSessionThreads(in IHintSession hintSession, in int[] tids); void setHintSessionThreads(in IHintSession hintSession, in int[] tids);
int[] getHintSessionThreadIds(in IHintSession hintSession); int[] getHintSessionThreadIds(in IHintSession hintSession);
/**
* Returns FMQ channel information for the caller, which it associates to a binder token.
*
* Throws IllegalStateException if FMQ channel creation fails.
*/
ChannelConfig getSessionChannel(in IBinder token);
oneway void closeSessionChannel();
} }
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#define LOG_TAG "PerformanceHintNativeTest" #define LOG_TAG "PerformanceHintNativeTest"
#include <aidl/android/hardware/power/ChannelConfig.h>
#include <aidl/android/hardware/power/SessionConfig.h> #include <aidl/android/hardware/power/SessionConfig.h>
#include <aidl/android/hardware/power/SessionTag.h> #include <aidl/android/hardware/power/SessionTag.h>
#include <aidl/android/hardware/power/WorkDuration.h> #include <aidl/android/hardware/power/WorkDuration.h>
...@@ -54,6 +55,9 @@ public: ...@@ -54,6 +55,9 @@ public:
MOCK_METHOD(ScopedAStatus, getHintSessionThreadIds, MOCK_METHOD(ScopedAStatus, getHintSessionThreadIds,
(const std::shared_ptr<IHintSession>& hintSession, ::std::vector<int32_t>* tids), (const std::shared_ptr<IHintSession>& hintSession, ::std::vector<int32_t>* tids),
(override)); (override));
MOCK_METHOD(ScopedAStatus, getSessionChannel,
(const ::ndk::SpAIBinder& in_token, hal::ChannelConfig* _aidl_return), (override));
MOCK_METHOD(ScopedAStatus, closeSessionChannel, (), (override));
MOCK_METHOD(SpAIBinder, asBinder, (), (override)); MOCK_METHOD(SpAIBinder, asBinder, (), (override));
MOCK_METHOD(bool, isRemote, (), (override)); MOCK_METHOD(bool, isRemote, (), (override));
}; };
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package com.android.server.power.hint; package com.android.server.power.hint;
import static android.os.Flags.adpfUseFmqChannel;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.power.hint.Flags.powerhintThreadCleanup; import static com.android.server.power.hint.Flags.powerhintThreadCleanup;
...@@ -26,6 +28,8 @@ import android.app.ActivityManagerInternal; ...@@ -26,6 +28,8 @@ import android.app.ActivityManagerInternal;
import android.app.StatsManager; import android.app.StatsManager;
import android.app.UidObserver; import android.app.UidObserver;
import android.content.Context; import android.content.Context;
import android.hardware.power.ChannelConfig;
import android.hardware.power.IPower;
import android.hardware.power.SessionConfig; import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag; import android.hardware.power.SessionTag;
import android.hardware.power.WorkDuration; import android.hardware.power.WorkDuration;
...@@ -39,6 +43,7 @@ import android.os.Message; ...@@ -39,6 +43,7 @@ import android.os.Message;
import android.os.PerformanceHintManager; import android.os.PerformanceHintManager;
import android.os.Process; import android.os.Process;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties; import android.os.SystemProperties;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.ArrayMap; import android.util.ArrayMap;
...@@ -69,6 +74,7 @@ import java.util.Map; ...@@ -69,6 +74,7 @@ import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
...@@ -91,9 +97,21 @@ public final class HintManagerService extends SystemService { ...@@ -91,9 +97,21 @@ public final class HintManagerService extends SystemService {
@GuardedBy("mLock") @GuardedBy("mLock")
private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions; private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions;
// Multi-level map storing all the channel binder token death listeners.
// First level is keyed by the UID of the client process owning the channel.
// Second level is the tgid of the process, which will often just be size one.
// Each channel is unique per (tgid, uid) pair, so this map associates each pair with an
// object that listens for the death notification of the binder token that was provided by
// that client when it created the channel, so we can detect when the client process dies.
@GuardedBy("mChannelMapLock")
private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap;
/** Lock to protect mActiveSessions and the UidObserver. */ /** Lock to protect mActiveSessions and the UidObserver. */
private final Object mLock = new Object(); private final Object mLock = new Object();
/** Lock to protect mChannelMap. */
private final Object mChannelMapLock = new Object();
@GuardedBy("mNonIsolatedTidsLock") @GuardedBy("mNonIsolatedTidsLock")
private final Map<Integer, Set<Long>> mNonIsolatedTids; private final Map<Integer, Set<Long>> mNonIsolatedTids;
...@@ -110,6 +128,9 @@ public final class HintManagerService extends SystemService { ...@@ -110,6 +128,9 @@ public final class HintManagerService extends SystemService {
private AtomicBoolean mConfigCreationSupport = new AtomicBoolean(true); private AtomicBoolean mConfigCreationSupport = new AtomicBoolean(true);
private final IPower mPowerHal;
private int mPowerHalVersion;
private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint"; private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint";
private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager"; private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
...@@ -131,12 +152,22 @@ public final class HintManagerService extends SystemService { ...@@ -131,12 +152,22 @@ public final class HintManagerService extends SystemService {
mNonIsolatedTids = null; mNonIsolatedTids = null;
} }
mActiveSessions = new ArrayMap<>(); mActiveSessions = new ArrayMap<>();
mChannelMap = new ArrayMap<>();
mNativeWrapper = injector.createNativeWrapper(); mNativeWrapper = injector.createNativeWrapper();
mNativeWrapper.halInit(); mNativeWrapper.halInit();
mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate(); mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate();
mUidObserver = new MyUidObserver(); mUidObserver = new MyUidObserver();
mAmInternal = Objects.requireNonNull( mAmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityManagerInternal.class)); LocalServices.getService(ActivityManagerInternal.class));
mPowerHal = injector.createIPower();
mPowerHalVersion = 0;
if (mPowerHal != null) {
try {
mPowerHalVersion = mPowerHal.getInterfaceVersion();
} catch (RemoteException e) {
throw new IllegalStateException("Could not contact PowerHAL!", e);
}
}
} }
private ServiceThread createCleanUpThread() { private ServiceThread createCleanUpThread() {
...@@ -151,6 +182,10 @@ public final class HintManagerService extends SystemService { ...@@ -151,6 +182,10 @@ public final class HintManagerService extends SystemService {
NativeWrapper createNativeWrapper() { NativeWrapper createNativeWrapper() {
return new NativeWrapper(); return new NativeWrapper();
} }
IPower createIPower() {
return IPower.Stub.asInterface(
ServiceManager.waitForDeclaredService(IPower.DESCRIPTOR + "/default"));
}
} }
private boolean isHalSupported() { private boolean isHalSupported() {
...@@ -344,6 +379,16 @@ public final class HintManagerService extends SystemService { ...@@ -344,6 +379,16 @@ public final class HintManagerService extends SystemService {
} }
} }
} }
synchronized (mChannelMapLock) {
// Clean up the uid's session channels
final TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid);
if (uidMap != null) {
for (Map.Entry<Integer, ChannelItem> entry : uidMap.entrySet()) {
entry.getValue().closeChannel();
}
mChannelMap.remove(uid);
}
}
}); });
} }
...@@ -383,6 +428,113 @@ public final class HintManagerService extends SystemService { ...@@ -383,6 +428,113 @@ public final class HintManagerService extends SystemService {
} }
} }
/**
* Creates a channel item in the channel map if one does not exist, then returns
* the entry in the channel map.
*/
public ChannelItem getOrCreateMappedChannelItem(int tgid, int uid, IBinder token) {
synchronized (mChannelMapLock) {
if (!mChannelMap.containsKey(uid)) {
mChannelMap.put(uid, new TreeMap<Integer, ChannelItem>());
}
TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid);
if (!map.containsKey(tgid)) {
ChannelItem item = new ChannelItem(tgid, uid, token);
item.openChannel();
map.put(tgid, item);
}
return map.get(tgid);
}
}
/**
* This removes an entry in the binder token callback map when a channel is closed,
* and unregisters its callbacks.
*/
public void removeChannelItem(Integer tgid, Integer uid) {
synchronized (mChannelMapLock) {
TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid);
if (map != null) {
ChannelItem item = map.get(tgid);
if (item != null) {
item.closeChannel();
map.remove(tgid);
}
if (map.isEmpty()) {
mChannelMap.remove(uid);
}
}
}
}
/**
* Manages the lifecycle of a single channel. This includes caching the channel descriptor,
* receiving binder token death notifications, and handling cleanup on uid termination. There
* can only be one ChannelItem per (tgid, uid) pair in mChannelMap, and channel creation happens
* when a ChannelItem enters the map, while destruction happens when it leaves the map.
*/
private class ChannelItem implements IBinder.DeathRecipient {
@Override
public void binderDied() {
removeChannelItem(mTgid, mUid);
}
ChannelItem(int tgid, int uid, IBinder token) {
this.mTgid = tgid;
this.mUid = uid;
this.mToken = token;
this.mLinked = false;
this.mConfig = null;
}
public void closeChannel() {
if (mLinked) {
mToken.unlinkToDeath(this, 0);
mLinked = false;
}
if (mConfig != null) {
try {
mPowerHal.closeSessionChannel(mTgid, mUid);
} catch (RemoteException e) {
throw new IllegalStateException("Failed to close session channel!", e);
}
mConfig = null;
}
}
public void openChannel() {
if (!mLinked) {
try {
mToken.linkToDeath(this, 0);
} catch (RemoteException e) {
throw new IllegalStateException("Client already dead", e);
}
mLinked = true;
}
if (mConfig == null) {
try {
// This method uses PowerHAL directly through the SDK,
// to avoid needing to pass the ChannelConfig through JNI.
mConfig = mPowerHal.getSessionChannel(mTgid, mUid);
} catch (RemoteException e) {
removeChannelItem(mTgid, mUid);
throw new IllegalStateException("Failed to create session channel!", e);
}
}
}
ChannelConfig getConfig() {
return mConfig;
}
// To avoid accidental double-linking / unlinking
boolean mLinked;
final int mTgid;
final int mUid;
final IBinder mToken;
ChannelConfig mConfig;
}
final class CleanUpHandler extends Handler { final class CleanUpHandler extends Handler {
// status of processed tid used for caching // status of processed tid used for caching
private static final int TID_NOT_CHECKED = 0; private static final int TID_NOT_CHECKED = 0;
...@@ -570,6 +722,18 @@ public final class HintManagerService extends SystemService { ...@@ -570,6 +722,18 @@ public final class HintManagerService extends SystemService {
return mService; return mService;
} }
@VisibleForTesting
Boolean hasChannel(int tgid, int uid) {
synchronized (mChannelMapLock) {
TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid);
if (uidMap != null) {
ChannelItem item = uidMap.get(tgid);
return item != null;
}
return false;
}
}
// returns the first invalid tid or null if not found // returns the first invalid tid or null if not found
private Integer checkTidValid(int uid, int tgid, int [] tids, IntArray nonIsolated) { private Integer checkTidValid(int uid, int tgid, int [] tids, IntArray nonIsolated) {
// Make sure all tids belongs to the same UID (including isolated UID), // Make sure all tids belongs to the same UID (including isolated UID),
...@@ -709,6 +873,28 @@ public final class HintManagerService extends SystemService { ...@@ -709,6 +873,28 @@ public final class HintManagerService extends SystemService {
} }
} }
@Override
public ChannelConfig getSessionChannel(IBinder token) {
if (mPowerHalVersion < 5 || !adpfUseFmqChannel()) {
return null;
}
java.util.Objects.requireNonNull(token);
final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
final int callingUid = Binder.getCallingUid();
ChannelItem item = getOrCreateMappedChannelItem(callingTgid, callingUid, token);
return item.getConfig();
};
@Override
public void closeSessionChannel() {
if (mPowerHalVersion < 5 || !adpfUseFmqChannel()) {
return;
}
final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
final int callingUid = Binder.getCallingUid();
removeChannelItem(callingTgid, callingUid);
};
@Override @Override
public long getHintSessionPreferredRate() { public long getHintSessionPreferredRate() {
return mHintSessionPreferredRate; return mHintSessionPreferredRate;
......
...@@ -34,11 +34,8 @@ ...@@ -34,11 +34,8 @@
#include "jni.h" #include "jni.h"
using aidl::android::hardware::power::SessionConfig; namespace hal = aidl::android::hardware::power;
using aidl::android::hardware::power::SessionHint;
using aidl::android::hardware::power::SessionMode;
using aidl::android::hardware::power::SessionTag;
using aidl::android::hardware::power::WorkDuration;
using android::power::PowerHintSessionWrapper; using android::power::PowerHintSessionWrapper;
namespace android { namespace android {
...@@ -95,10 +92,11 @@ static jlong createHintSession(JNIEnv* env, int32_t tgid, int32_t uid, ...@@ -95,10 +92,11 @@ static jlong createHintSession(JNIEnv* env, int32_t tgid, int32_t uid,
static jlong createHintSessionWithConfig(JNIEnv* env, int32_t tgid, int32_t uid, static jlong createHintSessionWithConfig(JNIEnv* env, int32_t tgid, int32_t uid,
std::vector<int32_t> threadIds, int64_t durationNanos, std::vector<int32_t> threadIds, int64_t durationNanos,
int32_t sessionTag, SessionConfig& config) { int32_t sessionTag, hal::SessionConfig& config) {
auto result = auto result =
gPowerHalController.createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, gPowerHalController.createHintSessionWithConfig(tgid, uid, threadIds, durationNanos,
static_cast<SessionTag>(sessionTag), static_cast<hal::SessionTag>(
sessionTag),
&config); &config);
if (result.isOk()) { if (result.isOk()) {
jlong session_ptr = reinterpret_cast<jlong>(result.value().get()); jlong session_ptr = reinterpret_cast<jlong>(result.value().get());
...@@ -140,12 +138,12 @@ static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDuration ...@@ -140,12 +138,12 @@ static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDuration
} }
static void reportActualWorkDuration(int64_t session_ptr, static void reportActualWorkDuration(int64_t session_ptr,
const std::vector<WorkDuration>& actualDurations) { const std::vector<hal::WorkDuration>& actualDurations) {
auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->reportActualWorkDuration(actualDurations); appSession->reportActualWorkDuration(actualDurations);
} }
static void sendHint(int64_t session_ptr, SessionHint hint) { static void sendHint(int64_t session_ptr, hal::SessionHint hint) {
auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->sendHint(hint); appSession->sendHint(hint);
} }
...@@ -155,7 +153,7 @@ static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadId ...@@ -155,7 +153,7 @@ static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadId
appSession->setThreads(threadIds); appSession->setThreads(threadIds);
} }
static void setMode(int64_t session_ptr, SessionMode mode, bool enabled) { static void setMode(int64_t session_ptr, hal::SessionMode mode, bool enabled) {
auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->setMode(mode, enabled); appSession->setMode(mode, enabled);
} }
...@@ -189,7 +187,7 @@ static jlong nativeCreateHintSessionWithConfig(JNIEnv* env, jclass /* clazz */, ...@@ -189,7 +187,7 @@ static jlong nativeCreateHintSessionWithConfig(JNIEnv* env, jclass /* clazz */,
return 0; return 0;
} }
std::vector<int32_t> threadIds(tidArray.get(), tidArray.get() + tidArray.size()); std::vector<int32_t> threadIds(tidArray.get(), tidArray.get() + tidArray.size());
SessionConfig config; hal::SessionConfig config;
jlong out = createHintSessionWithConfig(env, tgid, uid, std::move(threadIds), durationNanos, jlong out = createHintSessionWithConfig(env, tgid, uid, std::move(threadIds), durationNanos,
sessionTag, config); sessionTag, config);
if (out <= 0) { if (out <= 0) {
...@@ -223,7 +221,7 @@ static void nativeReportActualWorkDuration(JNIEnv* env, jclass /* clazz */, jlon ...@@ -223,7 +221,7 @@ static void nativeReportActualWorkDuration(JNIEnv* env, jclass /* clazz */, jlon
ScopedLongArrayRO arrayActualDurations(env, actualDurations); ScopedLongArrayRO arrayActualDurations(env, actualDurations);
ScopedLongArrayRO arrayTimeStamps(env, timeStamps); ScopedLongArrayRO arrayTimeStamps(env, timeStamps);
std::vector<WorkDuration> actualList(arrayActualDurations.size()); std::vector<hal::WorkDuration> actualList(arrayActualDurations.size());
for (size_t i = 0; i < arrayActualDurations.size(); i++) { for (size_t i = 0; i < arrayActualDurations.size(); i++) {
actualList[i].timeStampNanos = arrayTimeStamps[i]; actualList[i].timeStampNanos = arrayTimeStamps[i];
actualList[i].durationNanos = arrayActualDurations[i]; actualList[i].durationNanos = arrayActualDurations[i];
...@@ -232,7 +230,7 @@ static void nativeReportActualWorkDuration(JNIEnv* env, jclass /* clazz */, jlon ...@@ -232,7 +230,7 @@ static void nativeReportActualWorkDuration(JNIEnv* env, jclass /* clazz */, jlon
} }
static void nativeSendHint(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint hint) { static void nativeSendHint(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint hint) {
sendHint(session_ptr, static_cast<SessionHint>(hint)); sendHint(session_ptr, static_cast<hal::SessionHint>(hint));
} }
static void nativeSetThreads(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jintArray tids) { static void nativeSetThreads(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jintArray tids) {
...@@ -244,13 +242,13 @@ static void nativeSetThreads(JNIEnv* env, jclass /* clazz */, jlong session_ptr, ...@@ -244,13 +242,13 @@ static void nativeSetThreads(JNIEnv* env, jclass /* clazz */, jlong session_ptr,
static void nativeSetMode(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint mode, static void nativeSetMode(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint mode,
jboolean enabled) { jboolean enabled) {
setMode(session_ptr, static_cast<SessionMode>(mode), enabled); setMode(session_ptr, static_cast<hal::SessionMode>(mode), enabled);
} }
static void nativeReportActualWorkDuration2(JNIEnv* env, jclass /* clazz */, jlong session_ptr, static void nativeReportActualWorkDuration2(JNIEnv* env, jclass /* clazz */, jlong session_ptr,
jobjectArray jWorkDurations) { jobjectArray jWorkDurations) {
int size = env->GetArrayLength(jWorkDurations); int size = env->GetArrayLength(jWorkDurations);
std::vector<WorkDuration> workDurations(size); std::vector<hal::WorkDuration> workDurations(size);
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
jobject workDuration = env->GetObjectArrayElement(jWorkDurations, i); jobject workDuration = env->GetObjectArrayElement(jWorkDurations, i);
workDurations[i].workPeriodStartTimestampNanos = workDurations[i].workPeriodStartTimestampNanos =
......
...@@ -26,12 +26,15 @@ import static org.junit.Assert.assertEquals; ...@@ -26,12 +26,15 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq; import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset;
...@@ -39,9 +42,12 @@ import static org.mockito.Mockito.times; ...@@ -39,9 +42,12 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal;
import android.content.Context; import android.content.Context;
import android.hardware.power.ChannelConfig;
import android.hardware.power.IPower;
import android.hardware.power.SessionConfig; import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag; import android.hardware.power.SessionTag;
import android.hardware.power.WorkDuration; import android.hardware.power.WorkDuration;
...@@ -50,6 +56,7 @@ import android.os.IBinder; ...@@ -50,6 +56,7 @@ import android.os.IBinder;
import android.os.IHintSession; import android.os.IHintSession;
import android.os.PerformanceHintManager; import android.os.PerformanceHintManager;
import android.os.Process; import android.os.Process;
import android.os.RemoteException;
import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.DeviceFlagsValueProvider;
...@@ -130,12 +137,15 @@ public class HintManagerServiceTest { ...@@ -130,12 +137,15 @@ public class HintManagerServiceTest {
@Mock @Mock
private HintManagerService.NativeWrapper mNativeWrapperMock; private HintManagerService.NativeWrapper mNativeWrapperMock;
@Mock @Mock
private IPower mIPowerMock;
@Mock
private ActivityManagerInternal mAmInternalMock; private ActivityManagerInternal mAmInternalMock;
@Rule @Rule
public final CheckFlagsRule mCheckFlagsRule = public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule(); DeviceFlagsValueProvider.createCheckFlagsRule();
private HintManagerService mService; private HintManagerService mService;
private ChannelConfig mConfig;
private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) { private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) {
return new Answer<Long>() { return new Answer<Long>() {
...@@ -149,6 +159,9 @@ public class HintManagerServiceTest { ...@@ -149,6 +159,9 @@ public class HintManagerServiceTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mConfig = new ChannelConfig();
mConfig.readFlagBitmask = 1;
mConfig.writeFlagBitmask = 2;
when(mNativeWrapperMock.halGetHintSessionPreferredRate()) when(mNativeWrapperMock.halGetHintSessionPreferredRate())
.thenReturn(DEFAULT_HINT_PREFERRED_RATE); .thenReturn(DEFAULT_HINT_PREFERRED_RATE);
when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A), when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A),
...@@ -170,6 +183,8 @@ public class HintManagerServiceTest { ...@@ -170,6 +183,8 @@ public class HintManagerServiceTest {
any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[2], any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[2],
SESSION_IDS[2])); SESSION_IDS[2]));
when(mIPowerMock.getInterfaceVersion()).thenReturn(5);
when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig);
LocalServices.removeServiceForTest(ActivityManagerInternal.class); LocalServices.removeServiceForTest(ActivityManagerInternal.class);
LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock); LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
} }
...@@ -252,6 +267,9 @@ public class HintManagerServiceTest { ...@@ -252,6 +267,9 @@ public class HintManagerServiceTest {
NativeWrapper createNativeWrapper() { NativeWrapper createNativeWrapper() {
return mNativeWrapperMock; return mNativeWrapperMock;
} }
IPower createIPower() {
return mIPowerMock;
}
}); });
return mService; return mService;
} }
...@@ -261,6 +279,9 @@ public class HintManagerServiceTest { ...@@ -261,6 +279,9 @@ public class HintManagerServiceTest {
NativeWrapper createNativeWrapper() { NativeWrapper createNativeWrapper() {
return new NativeWrapperFake(); return new NativeWrapperFake();
} }
IPower createIPower() {
return mIPowerMock;
}
}); });
return mService; return mService;
} }
...@@ -728,6 +749,102 @@ public class HintManagerServiceTest { ...@@ -728,6 +749,102 @@ public class HintManagerServiceTest {
verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean()); verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean());
} }
@Test
public void testGetChannel() throws Exception {
HintManagerService service = createService();
Binder token = new Binder();
// Should only call once, after caching the first call
ChannelConfig config = service.getBinderServiceInstance().getSessionChannel(token);
ChannelConfig config2 = service.getBinderServiceInstance().getSessionChannel(token);
verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
assertEquals(config.readFlagBitmask, mConfig.readFlagBitmask);
assertEquals(config.writeFlagBitmask, mConfig.writeFlagBitmask);
assertEquals(config2.readFlagBitmask, mConfig.readFlagBitmask);
assertEquals(config2.writeFlagBitmask, mConfig.writeFlagBitmask);
}
@Test
public void testGetChannelTwice() throws Exception {
HintManagerService service = createService();
Binder token = new Binder();
service.getBinderServiceInstance().getSessionChannel(token);
verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
service.getBinderServiceInstance().closeSessionChannel();
verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
clearInvocations(mIPowerMock);
service.getBinderServiceInstance().getSessionChannel(token);
verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
service.getBinderServiceInstance().closeSessionChannel();
verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
}
@Test
public void testGetChannelFails() throws Exception {
HintManagerService service = createService();
Binder token = new Binder();
when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenThrow(RemoteException.class);
assertThrows(IllegalStateException.class, () -> {
service.getBinderServiceInstance().getSessionChannel(token);
});
}
@Test
public void testGetChannelBadVersion() throws Exception {
when(mIPowerMock.getInterfaceVersion()).thenReturn(3);
HintManagerService service = createService();
Binder token = new Binder();
reset(mIPowerMock);
when(mIPowerMock.getInterfaceVersion()).thenReturn(3);
when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig);
ChannelConfig channel = service.getBinderServiceInstance().getSessionChannel(token);
verify(mIPowerMock, times(0)).getSessionChannel(eq(TGID), eq(UID));
assertNull(channel);
}
@Test
public void testCloseChannel() throws Exception {
HintManagerService service = createService();
Binder token = new Binder();
service.getBinderServiceInstance().getSessionChannel(token);
service.getBinderServiceInstance().closeSessionChannel();
verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
}
@Test
public void testCloseChannelFails() throws Exception {
HintManagerService service = createService();
Binder token = new Binder();
service.getBinderServiceInstance().getSessionChannel(token);
doThrow(RemoteException.class).when(mIPowerMock).closeSessionChannel(anyInt(), anyInt());
assertThrows(IllegalStateException.class, () -> {
service.getBinderServiceInstance().closeSessionChannel();
});
}
@Test
public void testDoubleClose() throws Exception {
HintManagerService service = createService();
Binder token = new Binder();
service.getBinderServiceInstance().getSessionChannel(token);
service.getBinderServiceInstance().closeSessionChannel();
service.getBinderServiceInstance().closeSessionChannel();
verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
}
// This test checks that concurrent operations from different threads on IHintService, // This test checks that concurrent operations from different threads on IHintService,
// IHintSession and UidObserver will not cause data race or deadlock. Ideally we should also // IHintSession and UidObserver will not cause data race or deadlock. Ideally we should also
// check the output of threads' reportActualDuration performance to detect lock starvation // check the output of threads' reportActualDuration performance to detect lock starvation
...@@ -935,4 +1052,40 @@ public class HintManagerServiceTest { ...@@ -935,4 +1052,40 @@ public class HintManagerServiceTest {
a.reportActualWorkDuration2(WORK_DURATIONS_FIVE); a.reportActualWorkDuration2(WORK_DURATIONS_FIVE);
verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any()); verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any());
} }
@Test
public void testChannelDiesWhenTokenDies() throws Exception {
HintManagerService service = createService();
class DyingToken extends Binder {
DeathRecipient mToNotify;
@Override
public void linkToDeath(@NonNull DeathRecipient recipient, int flags) {
mToNotify = recipient;
super.linkToDeath(recipient, flags);
}
public void fakeDeath() {
mToNotify.binderDied();
}
}
DyingToken token = new DyingToken();
service.getBinderServiceInstance().getSessionChannel(token);
verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
assertTrue(service.hasChannel(TGID, UID));
token.fakeDeath();
assertFalse(service.hasChannel(TGID, UID));
verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID));
clearInvocations(mIPowerMock);
token = new DyingToken();
service.getBinderServiceInstance().getSessionChannel(token);
verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID));
assertTrue(service.hasChannel(TGID, UID));
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment