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

Commit 8c4cf9e0 authored by Hyundo Moon's avatar Hyundo Moon
Browse files

Make RouteSessionController as inner class of MediaRouter2

Bug: 146400872
Test: atest mediaroutertest
Change-Id: Ide7918b04dfec2fe0d1d5c07689dfd4f781fcf7a
parent a342e3c8
Loading
Loading
Loading
Loading
+174 −13
Original line number Diff line number Diff line
@@ -291,13 +291,15 @@ public class MediaRouter2 {
     * @param executor the executor to get the result of the session creation
     * @param callback the callback to get the result of the session creation
     *
     * @see SessionCreationCallback#onSessionCreated(RouteSessionController, Bundle)
     * @see SessionCreationCallback#onSessionCreationFailed()
     * @see SessionCallback#onSessionCreated(RouteSessionController, Bundle)
     * @see SessionCallback#onSessionCreationFailed()
     *
     * TODO: Separate callback registeration from creating session request.
     */
    @NonNull
    public void requestCreateSession(@NonNull MediaRoute2Info route,
            @NonNull String controlCategory,
            @CallbackExecutor Executor executor, @NonNull SessionCreationCallback callback) {
            @CallbackExecutor Executor executor, @NonNull SessionCallback callback) {
        Objects.requireNonNull(route, "route must not be null");
        if (TextUtils.isEmpty(controlCategory)) {
            throw new IllegalArgumentException("controlCategory must not be empty");
@@ -488,9 +490,9 @@ public class MediaRouter2 {
    }

    /**
     * Creates a controller and calls the {@link SessionCreationCallback#onSessionCreated}.
     * Creates a controller and calls the {@link SessionCallback#onSessionCreated}.
     * If session creation has failed, then it calls
     * {@link SessionCreationCallback#onSessionCreationFailed()}.
     * {@link SessionCallback#onSessionCreationFailed()}.
     * <p>
     * Pass {@code null} to sessionInfo for the failure case.
     */
@@ -512,7 +514,7 @@ public class MediaRouter2 {
        mSessionCreationRequests.remove(matchingRequest);

        final Executor executor = matchingRequest.mExecutor;
        final SessionCreationCallback callback = matchingRequest.mSessionCreationCallback;
        final SessionCallback callback = matchingRequest.mSessionCallback;

        if (sessionInfo == null) {
            // TODO: We may need to distinguish between failure and rejection.
@@ -580,9 +582,9 @@ public class MediaRouter2 {
    }

    /**
     * Callback for receiving a result of session creation.
     * Callback for receiving a result of session creation and session updates.
     */
    public static class SessionCreationCallback {
    public static class SessionCallback {
        /**
         * Called when the route session is created by the route provider.
         *
@@ -594,6 +596,166 @@ public class MediaRouter2 {
         * Called when the session creation request failed.
         */
        public void onSessionCreationFailed() {}

        /**
         * Called when the session info has changed.
         */
        void onSessionInfoChanged(RouteSessionController controller, RouteSessionInfo newInfo,
                RouteSessionInfo oldInfo) {}

        /**
         * Called when the session is released. Session can be released by the controller using
         * {@link RouteSessionController#release(boolean)}, or by the
         * {@link MediaRoute2ProviderService} itself. One can do clean-ups here.
         *
         * TODO: When Provider#notifySessionDestroyed is introduced, add @see for the method.
         */
        void onSessionReleased(RouteSessionController controller, int reason, boolean shouldStop) {}
    }

    /**
     * A class to control media route session in media route provider.
     * For example, adding/removing/transferring routes to session can be done through this class.
     * Instances are created by {@link MediaRouter2}.
     *
     * @hide
     */
    public final class RouteSessionController {
        private final Object mLock = new Object();

        @GuardedBy("mLock")
        private RouteSessionInfo mSessionInfo;

        @GuardedBy("mLock")
        private volatile boolean mIsReleased;

        RouteSessionController(@NonNull RouteSessionInfo sessionInfo) {
            mSessionInfo = sessionInfo;
        }

        /**
         * @return the ID of this controller
         */
        public int getSessionId() {
            synchronized (mLock) {
                return mSessionInfo.getSessionId();
            }
        }

        /**
         * @return the category of routes that the session includes.
         */
        @NonNull
        public String getControlCategory() {
            synchronized (mLock) {
                return mSessionInfo.getControlCategory();
            }
        }

        /**
         * @return the control hints used to control route session if available.
         */
        @Nullable
        public Bundle getControlHints() {
            synchronized (mLock) {
                return mSessionInfo.getControlHints();
            }
        }

        /**
         * @return the unmodifiable list of IDs of currently selected routes
         */
        @NonNull
        public List<String> getSelectedRoutes() {
            synchronized (mLock) {
                return Collections.unmodifiableList(mSessionInfo.getSelectedRoutes());
            }
        }

        /**
         * @return the unmodifiable list of IDs of deselectable routes for the session.
         */
        @NonNull
        public List<String> getDeselectableRoutes() {
            synchronized (mLock) {
                return Collections.unmodifiableList(mSessionInfo.getDeselectableRoutes());
            }
        }

        /**
         * @return the unmodifiable list of IDs of groupable routes for the session.
         */
        @NonNull
        public List<String> getGroupableRoutes() {
            synchronized (mLock) {
                return Collections.unmodifiableList(mSessionInfo.getGroupableRoutes());
            }
        }

        /**
         * @return the unmodifiable list of IDs of transferrable routes for the session.
         */
        @NonNull
        public List<String> getTransferrableRoutes() {
            synchronized (mLock) {
                return Collections.unmodifiableList(mSessionInfo.getTransferrableRoutes());
            }
        }

        /**
         * Returns true if the session is released, false otherwise.
         * If it is released, then all other getters from this instance may return invalid values.
         * Also, any operations to this instance will be ignored once released.
         *
         * @see #release
         * @see SessionCallback#onSessionReleased
         */
        public boolean isReleased() {
            synchronized (mLock) {
                return mIsReleased;
            }
        }

        /**
         * Add routes to the remote session. Route add requests that are currently in
         * {@link #getSelectedRoutes()} will be ignored.
         *
         * @see #getSelectedRoutes()
         * @see SessionCallback#onSessionInfoChanged
         */
        public void addRoute(MediaRoute2Info route) {
            // TODO: Implement this when the actual connection logic is implemented.
        }

        /**
         * Remove routes from this session. Media may be stopped on those devices.
         * Route removal requests that are not currently in {@link #getSelectedRoutes()} will be
         * ignored.
         *
         * @see #getSelectedRoutes()
         * @see SessionCallback#onSessionInfoChanged
         */
        public void removeRoute(MediaRoute2Info route) {
            // TODO: Implement this when the actual connection logic is implemented.
        }

        /**
         * Release this session.
         * Any operation on this session after calling this method will be ignored.
         *
         * @param stopMedia Should the media that is playing on the device be stopped after this
         *                  session is released.
         * @see SessionCallback#onSessionReleased
         */
        public void release(boolean stopMedia) {
            synchronized (mLock) {
                if (mIsReleased) {
                    return;
                }
                mIsReleased = true;
            }
            // TODO: Use stopMedia variable when the actual connection logic is implemented.
        }
    }

    final class RouteCallbackRecord {
@@ -616,8 +778,7 @@ public class MediaRouter2 {
            if (!(obj instanceof RouteCallbackRecord)) {
                return false;
            }
            return mRouteCallback
                    == ((RouteCallbackRecord) obj).mRouteCallback;
            return mRouteCallback == ((RouteCallbackRecord) obj).mRouteCallback;
        }

        @Override
@@ -630,17 +791,17 @@ public class MediaRouter2 {
        public final MediaRoute2Info mRoute;
        public final String mControlCategory;
        public final Executor mExecutor;
        public final SessionCreationCallback mSessionCreationCallback;
        public final SessionCallback mSessionCallback;
        public final int mRequestId;

        SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route,
                @NonNull String controlCategory,
                @Nullable Executor executor,
                @NonNull SessionCreationCallback sessionCreationCallback) {
                @NonNull SessionCallback sessionCallback) {
            mRoute = route;
            mControlCategory = controlCategory;
            mExecutor = executor;
            mSessionCreationCallback = sessionCreationCallback;
            mSessionCallback = sessionCallback;
            mRequestId = requestId;
        }
    }
+0 −225
Original line number Diff line number Diff line
/*
 * Copyright 2019 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.media;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;


import com.android.internal.annotations.GuardedBy;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;

/**
 * A class to control media route session in media route provider.
 * For example, adding/removing/transferring routes to session can be done through this class.
 * Instances are created by {@link MediaRouter2}.
 *
 * TODO: When session is introduced, change Javadoc of all methods/classes by using [@link Session].
 *
 * @hide
 */
public class RouteSessionController {
    private final int mSessionId;
    private final String mCategory;
    private final Object mLock = new Object();
    private final Bundle mControlHints;

    private List<String> mSelectedRoutes;

    @GuardedBy("mLock")
    private final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords =
            new CopyOnWriteArrayList<>();

    private volatile boolean mIsReleased;

    /**
     * @param sessionInfo
     */
    RouteSessionController(@NonNull RouteSessionInfo sessionInfo) {
        mSessionId = sessionInfo.getSessionId();
        mCategory = sessionInfo.getControlCategory();
        mSelectedRoutes = sessionInfo.getSelectedRoutes();
        mControlHints = sessionInfo.getControlHints();
        // TODO: Create getters for all other types of routes
    }

    /**
     * @return the ID of this controller
     */
    public int getSessionId() {
        return mSessionId;
    }

    /**
     * @return the category of routes that the session includes.
     */
    @NonNull
    public String getCategory() {
        return mCategory;
    }

    /**
     * @return the control hints used to control route session if available.
     */
    @Nullable
    public Bundle getControlHints() {
        return mControlHints;
    }

    /**
     * @return the list of currently selected routes
     */
    @NonNull
    public List<String> getSelectedRoutes() {
        return Collections.unmodifiableList(mSelectedRoutes);
    }

    /**
     * Returns true if the session is released, false otherwise.
     * If it is released, then all other getters from this instance may return invalid values.
     * Also, any operations to this instance will be ignored once released.
     *
     * @see #release
     * @see Callback#onReleased
     */
    public boolean isReleased() {
        return mIsReleased;
    }

    /**
     * Add routes to the remote session.
     *
     * @see #getSelectedRoutes()
     * @see Callback#onSessionInfoChanged
     */
    public void addRoutes(List<MediaRoute2Info> routes) {
        // TODO: Implement this when the actual connection logic is implemented.
    }

    /**
     * Remove routes from this session. Media may be stopped on those devices.
     * Route removal requests that are not currently in {@link #getSelectedRoutes()} will be
     * ignored.
     *
     * @see #getSelectedRoutes()
     * @see Callback#onSessionInfoChanged
     */
    public void removeRoutes(List<MediaRoute2Info> routes) {
        // TODO: Implement this when the actual connection logic is implemented.
    }

    /**
     * Registers a {@link Callback} for monitoring route changes.
     * If the same callback is registered previously, previous executor will be overwritten with the
     * new one.
     */
    public void registerCallback(Executor executor, Callback callback) {
        if (mIsReleased) {
            return;
        }
        Objects.requireNonNull(executor, "executor must not be null");
        Objects.requireNonNull(callback, "callback must not be null");

        synchronized (mLock) {
            CallbackRecord recordWithSameCallback = null;
            for (CallbackRecord record : mCallbackRecords) {
                if (callback == record.mCallback) {
                    recordWithSameCallback = record;
                    break;
                }
            }

            if (recordWithSameCallback != null) {
                recordWithSameCallback.mExecutor = executor;
            } else {
                mCallbackRecords.add(new CallbackRecord(executor, callback));
            }
        }
    }

    /**
     * Unregisters a previously registered {@link Callback}.
     */
    public void unregisterCallback(Callback callback) {
        Objects.requireNonNull(callback, "callback must not be null");

        synchronized (mLock) {
            CallbackRecord recordToRemove = null;
            for (CallbackRecord record : mCallbackRecords) {
                if (callback == record.mCallback) {
                    recordToRemove = record;
                    break;
                }
            }

            if (recordToRemove != null) {
                mCallbackRecords.remove(recordToRemove);
            }
        }
    }

    /**
     * Release this session.
     * Any operation on this session after calling this method will be ignored.
     *
     * @param stopMedia Should the device where the media is played
     *                  be stopped after this session is released.
     */
    public void release(boolean stopMedia) {
        mIsReleased = true;
        mCallbackRecords.clear();
        // TODO: Use stopMedia variable when the actual connection logic is implemented.
    }

    /**
     * Callback class for getting updates on routes and session release.
     */
    public static class Callback {

        /**
         * Called when the session info has changed.
         * TODO: When SessionInfo is introduced, uncomment below argument.
         */
        void onSessionInfoChanged(/* SessionInfo info */) {}

        /**
         * Called when the session is released. Session can be released by the controller using
         * {@link #release(boolean)}, or by the {@link MediaRoute2ProviderService} itself.
         * One can do clean-ups here.
         *
         * TODO: When SessionInfo is introduced, change the javadoc of releasing session on
         * provider side.
         */
        void onReleased(int reason, boolean shouldStop) {}
    }

    private class CallbackRecord {
        public final Callback mCallback;
        public Executor mExecutor;

        CallbackRecord(@NonNull Executor executor, @NonNull Callback callback) {
            mExecutor = executor;
            mCallback = callback;
        }
    }
}
+6 −6
Original line number Diff line number Diff line
@@ -43,8 +43,8 @@ import static org.testng.Assert.assertThrows;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2;
import android.media.MediaRouter2.SessionCreationCallback;
import android.media.RouteSessionController;
import android.media.MediaRouter2.RouteSessionController;
import android.media.MediaRouter2.SessionCallback;
import android.net.Uri;
import android.os.Parcel;
import android.support.test.InstrumentationRegistry;
@@ -209,7 +209,7 @@ public class MediaRouter2Test {
        MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build();
        String controlCategory = "controlCategory";
        Executor executor = mExecutor;
        MediaRouter2.SessionCreationCallback callback = new MediaRouter2.SessionCreationCallback();
        SessionCallback callback = new SessionCallback();

        // Tests null route
        assertThrows(NullPointerException.class,
@@ -243,12 +243,12 @@ public class MediaRouter2Test {
        final CountDownLatch failureLatch = new CountDownLatch(1);

        // Create session with this route
        SessionCreationCallback callback = new SessionCreationCallback() {
        SessionCallback callback = new SessionCallback() {
            @Override
            public void onSessionCreated(RouteSessionController controller) {
                assertNotNull(controller);
                assertTrue(controller.getSelectedRoutes().contains(ROUTE_ID1));
                assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getCategory()));
                assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory()));
                successLatch.countDown();
            }

@@ -281,7 +281,7 @@ public class MediaRouter2Test {
        final CountDownLatch failureLatch = new CountDownLatch(1);

        // Create session with this route
        SessionCreationCallback callback = new SessionCreationCallback() {
        SessionCallback callback = new SessionCallback() {
            @Override
            public void onSessionCreated(RouteSessionController controller) {
                successLatch.countDown();