Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 25318b73 authored by Jimmy Shiu's avatar Jimmy Shiu Committed by Wei Wang
Browse files

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: default avatarWei Wang <wvw@google.com>
parent 4c6e5036
Loading
Loading
Loading
Loading
+449 −0
Original line number Diff line number Diff line
/*
 * 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();
        }

    }
}
+4 −3
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
/*
 * 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 */
+1 −1
Original line number Diff line number Diff line
@@ -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,
+2 −0

File changed.

Preview size limit exceeded, changes collapsed.

Loading