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

Commit 55793f0c authored by Daniel Norman's avatar Daniel Norman Committed by Android (Google) Code Review
Browse files

Revert "Revert "Ensure MediaSession is ONLY made active when routed to wired headset.""

This reverts commit 3beead23.

Reason for revert: Re-landing with special case fix for accessibility

Change-Id: I04038fe9fc586e7d5995629aebf8b193d929a445
parent 3beead23
Loading
Loading
Loading
Loading
+48 −21
Original line number Original line Diff line number Diff line
@@ -23,11 +23,16 @@ import android.media.session.MediaSession;
import android.os.Handler;
import android.os.Handler;
import android.os.Looper;
import android.os.Looper;
import android.os.Message;
import android.os.Message;
import android.telecom.CallAudioState;
import android.telecom.CallEndpoint;
import android.telecom.Log;
import android.telecom.Log;
import android.util.ArraySet;
import android.view.KeyEvent;
import android.view.KeyEvent;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;


import java.util.Set;

/**
/**
 * Static class to handle listening to the headset media buttons.
 * Static class to handle listening to the headset media buttons.
 */
 */
@@ -149,8 +154,10 @@ public class HeadsetMediaButton extends CallsManagerListenerBase {
    private final Context mContext;
    private final Context mContext;
    private final CallsManager mCallsManager;
    private final CallsManager mCallsManager;
    private final TelecomSystem.SyncRoot mLock;
    private final TelecomSystem.SyncRoot mLock;
    private final Set<Call> mCalls = new ArraySet<>();
    private MediaSessionAdapter mSession;
    private MediaSessionAdapter mSession;
    private KeyEvent mLastHookEvent;
    private KeyEvent mLastHookEvent;
    private @CallEndpoint.EndpointType int mCurrentEndpointType;


    /**
    /**
     * Constructor used for testing purposes to initialize a {@link HeadsetMediaButton} with a
     * Constructor used for testing purposes to initialize a {@link HeadsetMediaButton} with a
@@ -212,7 +219,7 @@ public class HeadsetMediaButton extends CallsManagerListenerBase {
            return mCallsManager.onMediaButton(LONG_PRESS);
            return mCallsManager.onMediaButton(LONG_PRESS);
        } else if (event.getAction() == KeyEvent.ACTION_UP) {
        } else if (event.getAction() == KeyEvent.ACTION_UP) {
            // We should not judge SHORT_PRESS by ACTION_UP event repeatCount, because it always
            // We should not judge SHORT_PRESS by ACTION_UP event repeatCount, because it always
            // return 0.
            // returns 0.
            // Actually ACTION_DOWN event repeatCount only increases when LONG_PRESS performed.
            // Actually ACTION_DOWN event repeatCount only increases when LONG_PRESS performed.
            if (mLastHookEvent != null && mLastHookEvent.getRepeatCount() == 0) {
            if (mLastHookEvent != null && mLastHookEvent.getRepeatCount() == 0) {
                return mCallsManager.onMediaButton(SHORT_PRESS);
                return mCallsManager.onMediaButton(SHORT_PRESS);
@@ -226,50 +233,70 @@ public class HeadsetMediaButton extends CallsManagerListenerBase {
        return true;
        return true;
    }
    }


    @Override
    public void onCallEndpointChanged(CallEndpoint callEndpoint) {
        mCurrentEndpointType = callEndpoint.getEndpointType();
        Log.i(this, "onCallEndpointChanged: endPoint=%s", callEndpoint);
        maybeChangeSessionState();
    }

    /** ${inheritDoc} */
    /** ${inheritDoc} */
    @Override
    @Override
    public void onCallAdded(Call call) {
    public void onCallAdded(Call call) {
        if (call.isExternalCall()) {
        handleCallAddition(call);
            return;
        }
        handleCallAddition();
    }
    }


    /**
    /**
     * Triggers session activation due to call addition.
     * Triggers session activation due to call addition.
     */
     */
    private void handleCallAddition() {
    private void handleCallAddition(Call call) {
        mCalls.add(call);
        maybeChangeSessionState();
    }

    /**
     * Based on whether there are tracked calls and the audio is routed to a wired headset,
     * potentially activate or deactive the media session.
     */
    private void maybeChangeSessionState() {
        boolean hasNonExternalCalls = !mCalls.isEmpty()
                && mCalls.stream().anyMatch(c -> !c.isExternalCall());
        if (hasNonExternalCalls && mCurrentEndpointType == CallEndpoint.TYPE_WIRED_HEADSET) {
            Log.i(this, "maybeChangeSessionState: hasCalls=%b, currentEndpointType=%s, ACTIVATE",
                    hasNonExternalCalls, CallEndpoint.endpointTypeToString(mCurrentEndpointType));
            mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
            mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
        } else {
            Log.i(this, "maybeChangeSessionState: hasCalls=%b, currentEndpointType=%s, DEACTIVATE",
                    hasNonExternalCalls, CallEndpoint.endpointTypeToString(mCurrentEndpointType));
            mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 0, 0).sendToTarget();
        }
    }
    }


    /** ${inheritDoc} */
    /** ${inheritDoc} */
    @Override
    @Override
    public void onCallRemoved(Call call) {
    public void onCallRemoved(Call call) {
        if (call.isExternalCall()) {
        handleCallRemoval(call);
            return;
        }
        handleCallRemoval();
    }
    }


    /**
    /**
     * Triggers session deactivation due to call removal.
     * Triggers session deactivation due to call removal.
     */
     */
    private void handleCallRemoval() {
    private void handleCallRemoval(Call call) {
        if (!mCallsManager.hasAnyCalls()) {
        // If we were tracking the call, potentially change session state.
            mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 0, 0).sendToTarget();
        if (mCalls.remove(call)) {
            if (mCalls.isEmpty()) {
                // When there are no calls, don't cache that we previously had a wired headset
                // connected; we'll be updated on the next call.
                mCurrentEndpointType = CallEndpoint.TYPE_UNKNOWN;
            }
            maybeChangeSessionState();
        }
        }
    }
    }


    /** ${inheritDoc} */
    /** ${inheritDoc} */
    @Override
    @Override
    public void onExternalCallChanged(Call call, boolean isExternalCall) {
    public void onExternalCallChanged(Call call, boolean isExternalCall) {
        // Note: We don't use the onCallAdded/onCallRemoved methods here since they do checks to see
        maybeChangeSessionState();
        // if the call is external or not and would skip the session activation/deactivation.
        if (isExternalCall) {
            handleCallRemoval();
        } else {
            handleCallAddition();
        }
    }
    }


    @VisibleForTesting
    @VisibleForTesting
+90 −3
Original line number Original line Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.telecom.tests;


import android.content.Intent;
import android.content.Intent;
import android.media.session.MediaSession;
import android.media.session.MediaSession;
import android.telecom.CallEndpoint;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
import android.view.KeyEvent;
@@ -80,7 +81,7 @@ public class HeadsetMediaButtonTest extends TelecomTestCase {
    }
    }


    /**
    /**
     * Nominal case; just add a call and remove it.
     * Nominal case; just add a call and remove it; this happens when the audio state is earpiece.
     */
     */
    @SmallTest
    @SmallTest
    @Test
    @Test
@@ -90,14 +91,95 @@ public class HeadsetMediaButtonTest extends TelecomTestCase {
        when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
        when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
        mHeadsetMediaButton.onCallAdded(regularCall);
        mHeadsetMediaButton.onCallAdded(regularCall);
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter, never()).setActive(eq(true));

        // Report that the endpoint is earpiece and other routes that don't matter
        mHeadsetMediaButton.onCallEndpointChanged(
                new CallEndpoint("Earpiece", CallEndpoint.TYPE_EARPIECE));
        mHeadsetMediaButton.onCallEndpointChanged(
                new CallEndpoint("Speaker", CallEndpoint.TYPE_SPEAKER));
        mHeadsetMediaButton.onCallEndpointChanged(
                new CallEndpoint("BT", CallEndpoint.TYPE_BLUETOOTH));
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter, never()).setActive(eq(true));

        // ... and thus we see how the original code isn't amenable to tests.
        when(mMediaSessionAdapter.isActive()).thenReturn(false);

        // Still should not have done anything; we never hit wired headset
        mHeadsetMediaButton.onCallRemoved(regularCall);
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter, never()).setActive(eq(false));
    }

    /**
     * Call is added and then routed to headset after call start
     */
    @SmallTest
    @Test
    public void testAddCallThenRouteToHeadset() {
        Call regularCall = getRegularCall();

        mHeadsetMediaButton.onCallAdded(regularCall);
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter, never()).setActive(eq(true));

        mHeadsetMediaButton.onCallEndpointChanged(
                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter).setActive(eq(true));
        verify(mMediaSessionAdapter).setActive(eq(true));

        // ... and thus we see how the original code isn't amenable to tests.
        // ... and thus we see how the original code isn't amenable to tests.
        when(mMediaSessionAdapter.isActive()).thenReturn(true);
        when(mMediaSessionAdapter.isActive()).thenReturn(true);


        when(mMockCallsManager.hasAnyCalls()).thenReturn(false);
        // Remove the one call; we should release the session.
        mHeadsetMediaButton.onCallRemoved(regularCall);
        mHeadsetMediaButton.onCallRemoved(regularCall);
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter).setActive(eq(false));
        verify(mMediaSessionAdapter).setActive(eq(false));
        when(mMediaSessionAdapter.isActive()).thenReturn(false);

        // Add a new call; make sure we go active once more.
        mHeadsetMediaButton.onCallAdded(regularCall);
        mHeadsetMediaButton.onCallEndpointChanged(
                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter, times(2)).setActive(eq(true));
    }

    /**
     * Call is added and then routed to headset after call start
     */
    @SmallTest
    @Test
    public void testAddCallThenRouteToHeadsetAndBack() {
        Call regularCall = getRegularCall();

        when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
        mHeadsetMediaButton.onCallAdded(regularCall);
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter, never()).setActive(eq(true));

        mHeadsetMediaButton.onCallEndpointChanged(
                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter).setActive(eq(true));

        // ... and thus we see how the original code isn't amenable to tests.
        when(mMediaSessionAdapter.isActive()).thenReturn(true);

        mHeadsetMediaButton.onCallEndpointChanged(
                new CallEndpoint("Earpiece", CallEndpoint.TYPE_EARPIECE));
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter).setActive(eq(false));
        when(mMediaSessionAdapter.isActive()).thenReturn(false);

        // Remove the one call; we should not release again.
        mHeadsetMediaButton.onCallRemoved(regularCall);
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        // Remember, mockito counts total invocations; we should have went active once and then
        // inactive again when we hit earpiece.
        verify(mMediaSessionAdapter, times(1)).setActive(eq(true));
        verify(mMediaSessionAdapter, times(1)).setActive(eq(false));
    }
    }


    /**
    /**
@@ -111,6 +193,8 @@ public class HeadsetMediaButtonTest extends TelecomTestCase {
        // Start with a regular old call.
        // Start with a regular old call.
        when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
        when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
        mHeadsetMediaButton.onCallAdded(regularCall);
        mHeadsetMediaButton.onCallAdded(regularCall);
        mHeadsetMediaButton.onCallEndpointChanged(
                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter).setActive(eq(true));
        verify(mMediaSessionAdapter).setActive(eq(true));
        when(mMediaSessionAdapter.isActive()).thenReturn(true);
        when(mMediaSessionAdapter.isActive()).thenReturn(true);
@@ -122,6 +206,7 @@ public class HeadsetMediaButtonTest extends TelecomTestCase {
        // Expect to set session inactive.
        // Expect to set session inactive.
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter).setActive(eq(false));
        verify(mMediaSessionAdapter).setActive(eq(false));
        when(mMediaSessionAdapter.isActive()).thenReturn(false);


        // For good measure lets make it non-external again.
        // For good measure lets make it non-external again.
        when(regularCall.isExternalCall()).thenReturn(false);
        when(regularCall.isExternalCall()).thenReturn(false);
@@ -129,7 +214,7 @@ public class HeadsetMediaButtonTest extends TelecomTestCase {
        mHeadsetMediaButton.onExternalCallChanged(regularCall, false);
        mHeadsetMediaButton.onExternalCallChanged(regularCall, false);
        // Expect to set session active.
        // Expect to set session active.
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter).setActive(eq(true));
        verify(mMediaSessionAdapter, times(2)).setActive(eq(true));
    }
    }


    @MediumTest
    @MediumTest
@@ -139,6 +224,8 @@ public class HeadsetMediaButtonTest extends TelecomTestCase {
        when(externalCall.isExternalCall()).thenReturn(true);
        when(externalCall.isExternalCall()).thenReturn(true);


        mHeadsetMediaButton.onCallAdded(externalCall);
        mHeadsetMediaButton.onCallAdded(externalCall);
        mHeadsetMediaButton.onCallEndpointChanged(
                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter, never()).setActive(eq(true));
        verify(mMediaSessionAdapter, never()).setActive(eq(true));