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

Commit 510cdfc3 authored by Olivier Gaillard's avatar Olivier Gaillard
Browse files

Adds a mechanism to listen to proxy transact method calls.

There are multiple use cases for it:

1) Make it easy for another process to set the worksource. The
worksource can be propagated in a thread local - this is how gmscore and soon
system server works -  the worksource can then be set for all binder
calls using

Object transactStarted() {
    Binder.setWorkSource(ThreadLocalWorkSourceUid.get());
    return null;  // No token needed.
}

void transactEnded() {
    Binder.setWorkSource(null);
}

This will be used by system process and gmscore.

2) SystemUI team was interested in detecting binder calls done from the
main thread in dogfood/tests. This listener will make it easy to figure
out which thread is used.

Performance impact of transact method:
    - With current code: 45ns per call
    - With this code: 57ns per call
This is not significant compared to the total binder call time which is
10-100s of microseconds.

Test: unit test
Change-Id: Id0a2f52cba33b390ff83f703284b79471cc80b1c
parent 80665469
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
@@ -554,6 +554,46 @@ public class Binder implements IBinder {
        sDumpDisabled = msg;
    }

    /**
     * Listener to be notified about each proxy-side binder call.
     *
     * See {@link setProxyTransactListener}.
     * @hide
     */
    public interface ProxyTransactListener {
        /**
         * Called before onTransact.
         *
         * @return an object that will be passed back to #onTransactEnded (or null).
         */
        Object onTransactStarted(IBinder binder, int transactionCode);

        /**
         * Called after onTranact (even when an exception is thrown).
         *
         * @param session The object return by #onTransactStarted.
         */
        void onTransactEnded(@Nullable Object session);
    }

    /**
     * Sets a listener for the transact method on the proxy-side.
     *
     * <li>The listener is global. Only fast operations should be done to avoid thread
     * contentions.
     * <li>The listener implementation needs to handle synchronization if needed. The methods on the
     * listener can be called concurrently.
     * <li>Listener set will be used for new transactions. On-going transaction will still use the
     * previous listener (if already set).
     * <li>The listener is called on the critical path of the binder transaction so be careful about
     * performance.
     * <li>Never execute another binder transaction inside the listener.
     * @hide
     */
    public static void setProxyTransactListener(@Nullable ProxyTransactListener listener) {
        BinderProxy.setTransactListener(listener);
    }

    /**
     * Default implementation is a stub that returns false.  You will want
     * to override this to do the appropriate unmarshalling of transactions.
+22 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.os;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Log;
import android.util.SparseIntArray;

@@ -45,6 +46,15 @@ public final class BinderProxy implements IBinder {
    // Assume the process-wide default value when created
    volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;

    private static volatile Binder.ProxyTransactListener sTransactListener = null;

    /**
     * @see {@link Binder#setProxyTransactListener(listener)}.
     */
    public static void setTransactListener(@Nullable Binder.ProxyTransactListener listener) {
        sTransactListener = listener;
    }

    /*
     * Map from longs to BinderProxy, retaining only a WeakReference to the BinderProxies.
     * We roll our own only because we need to lazily remove WeakReferences during accesses
@@ -469,9 +479,21 @@ public final class BinderProxy implements IBinder {
            Trace.traceBegin(Trace.TRACE_TAG_ALWAYS,
                    stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName());
        }

        // Make sure the listener won't change while processing a transaction.
        final Binder.ProxyTransactListener transactListener = sTransactListener;
        Object session = null;
        if (transactListener != null) {
            session = transactListener.onTransactStarted(this, code);
        }

        try {
            return transactNative(code, data, reply, flags);
        } finally {
            if (transactListener != null) {
                transactListener.onTransactEnded(session);
            }

            if (tracingEnabled) {
                Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
            }
+88 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.os;

import android.annotation.Nullable;
import android.content.Context;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;

public class BinderProxyTest extends AndroidTestCase {
    private static class CountingListener implements Binder.ProxyTransactListener {
        int mStartedCount;
        int mEndedCount;

        public Object onTransactStarted(IBinder binder, int transactionCode) {
            mStartedCount++;
            return null;
        }

        public void onTransactEnded(@Nullable Object session) {
            mEndedCount++;
        }
    };

    private PowerManager mPowerManager;

    /**
     * Setup any common data for the upcoming tests.
     */
    @Override
    public void setUp() throws Exception {
        super.setUp();
        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
    }

    @MediumTest
    public void testNoListener() throws Exception {
        CountingListener listener = new CountingListener();
        Binder.setProxyTransactListener(listener);
        Binder.setProxyTransactListener(null);

        mPowerManager.isInteractive();

        assertEquals(0, listener.mStartedCount);
        assertEquals(0, listener.mEndedCount);
    }

    @MediumTest
    public void testListener() throws Exception {
        CountingListener listener = new CountingListener();
        Binder.setProxyTransactListener(listener);

        mPowerManager.isInteractive();

        assertEquals(1, listener.mStartedCount);
        assertEquals(1, listener.mEndedCount);
    }

    @MediumTest
    public void testSessionPropagated() throws Exception {
        Binder.setProxyTransactListener(new Binder.ProxyTransactListener() {
            public Object onTransactStarted(IBinder binder, int transactionCode) {
                return "foo";
            }

            public void onTransactEnded(@Nullable Object session) {
                assertEquals("foo", session);
            }
        });

        // Check it does not throw..
        mPowerManager.isInteractive();
    }
}