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

Commit 8b64d0c9 authored by Android Build Coastguard Worker's avatar Android Build Coastguard Worker
Browse files

Snap for 8345013 from a1367aa0 to tm-release

Change-Id: I6edc63d3d80aa980206abe108b14b7d2488809a1
parents aeb4c925 a1367aa0
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -3300,7 +3300,7 @@ public class CallsManager extends Call.ListenerBase
     *
     * @return {@code True} if there are any non-external calls, {@code false} otherwise.
     */
    boolean hasAnyCalls() {
    public boolean hasAnyCalls() {
        if (mCalls.isEmpty()) {
            return false;
        }
+97 −4
Original line number Diff line number Diff line
@@ -46,6 +46,47 @@ public class HeadsetMediaButton extends CallsManagerListenerBase {
    private static final int MSG_MEDIA_SESSION_INITIALIZE = 0;
    private static final int MSG_MEDIA_SESSION_SET_ACTIVE = 1;

    /**
     * Wrapper class that abstracts an instance of {@link MediaSession} to the
     * {@link MediaSessionAdapter} interface this class uses.  This is done because
     * {@link MediaSession} is a final class and cannot be mocked for testing purposes.
     */
    public class MediaSessionWrapper implements MediaSessionAdapter {
        private final MediaSession mMediaSession;

        public MediaSessionWrapper(MediaSession mediaSession) {
            mMediaSession = mediaSession;
        }

        /**
         * Sets the underlying {@link MediaSession} active status.
         * @param active
         */
        @Override
        public void setActive(boolean active) {
            mMediaSession.setActive(active);
        }

        /**
         * Gets the underlying {@link MediaSession} active status.
         * @return {@code true} if active, {@code false} otherwise.
         */
        @Override
        public boolean isActive() {
            return mMediaSession.isActive();
        }
    }

    /**
     * Interface which defines the basic functionality of a {@link MediaSession} which is important
     * for the {@link HeadsetMediaButton} to operator; this is for testing purposes so we can mock
     * out that functionality.
     */
    public interface MediaSessionAdapter {
        void setActive(boolean active);
        boolean isActive();
    }

    private final MediaSession.Callback mSessionCallback = new MediaSession.Callback() {
        @Override
        public boolean onMediaButtonEvent(Intent intent) {
@@ -81,7 +122,7 @@ public class HeadsetMediaButton extends CallsManagerListenerBase {
                    session.setFlags(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY
                            | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
                    session.setPlaybackToLocal(AUDIO_ATTRIBUTES);
                    mSession = session;
                    mSession = new MediaSessionWrapper(session);
                    break;
                }
                case MSG_MEDIA_SESSION_SET_ACTIVE: {
@@ -102,9 +143,37 @@ public class HeadsetMediaButton extends CallsManagerListenerBase {
    private final Context mContext;
    private final CallsManager mCallsManager;
    private final TelecomSystem.SyncRoot mLock;
    private MediaSession mSession;
    private MediaSessionAdapter mSession;
    private KeyEvent mLastHookEvent;

    /**
     * Constructor used for testing purposes to initialize a {@link HeadsetMediaButton} with a
     * specified {@link MediaSessionAdapter}.  Will not trigger MSG_MEDIA_SESSION_INITIALIZE and
     * cause an actual {@link MediaSession} instance to be created.
     * @param context the context
     * @param callsManager the mock calls manager
     * @param lock the lock
     * @param adapter the adapter
     */
    @VisibleForTesting
    public HeadsetMediaButton(
            Context context,
            CallsManager callsManager,
            TelecomSystem.SyncRoot lock,
            MediaSessionAdapter adapter) {
        mContext = context;
        mCallsManager = callsManager;
        mLock = lock;
        mSession = adapter;
    }

    /**
     * Production code constructor; this version triggers MSG_MEDIA_SESSION_INITIALIZE which will
     * create an actual instance of {@link MediaSession}.
     * @param context the context
     * @param callsManager the calls manager
     * @param lock the telecom lock
     */
    public HeadsetMediaButton(
            Context context,
            CallsManager callsManager,
@@ -155,6 +224,13 @@ public class HeadsetMediaButton extends CallsManagerListenerBase {
        if (call.isExternalCall()) {
            return;
        }
        handleCallAddition();
    }

    /**
     * Triggers session activation due to call addition.
     */
    private void handleCallAddition() {
        mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
    }

@@ -164,6 +240,13 @@ public class HeadsetMediaButton extends CallsManagerListenerBase {
        if (call.isExternalCall()) {
            return;
        }
        handleCallRemoval();
    }

    /**
     * Triggers session deactivation due to call removal.
     */
    private void handleCallRemoval() {
        if (!mCallsManager.hasAnyCalls()) {
            mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 0, 0).sendToTarget();
        }
@@ -172,10 +255,20 @@ public class HeadsetMediaButton extends CallsManagerListenerBase {
    /** ${inheritDoc} */
    @Override
    public void onExternalCallChanged(Call call, boolean isExternalCall) {
        // Note: We don't use the onCallAdded/onCallRemoved methods here since they do checks to see
        // if the call is external or not and would skip the session activation/deactivation.
        if (isExternalCall) {
            onCallRemoved(call);
            handleCallRemoval();
        } else {
            onCallAdded(call);
            handleCallAddition();
        }
    }

    @VisibleForTesting
    /**
     * @return the handler this class instance uses for operation; used for unit testing.
     */
    public Handler getHandler() {
        return mMediaSessionHandler;
    }
}
+120 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.telecom.tests;

import com.android.server.telecom.Call;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.HeadsetMediaButton;
import com.android.server.telecom.TelecomSystem;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.Mockito;

import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(JUnit4.class)
public class HeadsetMediaButtonTest extends TelecomTestCase {
    private static final int TEST_TIMEOUT_MILLIS = 1000;

    private HeadsetMediaButton mHeadsetMediaButton;

    @Mock private CallsManager mMockCallsManager;
    @Mock private HeadsetMediaButton.MediaSessionAdapter mMediaSessionAdapter;
    private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {};

    @Override
    @Before
    public void setUp() throws Exception {
        super.setUp();
        mHeadsetMediaButton = new HeadsetMediaButton(mContext, mMockCallsManager, mLock,
                mMediaSessionAdapter);
    }

    @Override
    @After
    public void tearDown() throws Exception {
        mHeadsetMediaButton = null;
        super.tearDown();
    }

    /**
     * Nominal case; just add a call and remove it.
     */
    @Test
    public void testAddCall() {
        Call regularCall = getRegularCall();

        when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
        mHeadsetMediaButton.onCallAdded(regularCall);
        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);

        when(mMockCallsManager.hasAnyCalls()).thenReturn(false);
        mHeadsetMediaButton.onCallRemoved(regularCall);
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter).setActive(eq(false));
    }

    /**
     * Test a case where a regular call becomes an external call, and back again.
     */
    @Test
    public void testRegularCallThatBecomesExternal() {
        Call regularCall = getRegularCall();

        // Start with a regular old call.
        when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
        mHeadsetMediaButton.onCallAdded(regularCall);
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter).setActive(eq(true));
        when(mMediaSessionAdapter.isActive()).thenReturn(true);

        // Change so it is external.
        when(regularCall.isExternalCall()).thenReturn(true);
        when(mMockCallsManager.hasAnyCalls()).thenReturn(false);
        mHeadsetMediaButton.onExternalCallChanged(regularCall, true);
        // Expect to set session inactive.
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter).setActive(eq(false));

        // For good measure lets make it non-external again.
        when(regularCall.isExternalCall()).thenReturn(false);
        when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
        mHeadsetMediaButton.onExternalCallChanged(regularCall, false);
        // Expect to set session active.
        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
        verify(mMediaSessionAdapter).setActive(eq(true));
    }

    /**
     * @return a mock call instance of a regular non-external call.
     */
    private Call getRegularCall() {
        Call regularCall = Mockito.mock(Call.class);
        when(regularCall.isExternalCall()).thenReturn(false);
        return regularCall;
    }
}