Loading media/java/android/media/MediaRouter2.java +174 −13 Original line number Original line Diff line number Diff line Loading @@ -291,13 +291,15 @@ public class MediaRouter2 { * @param executor the executor to get the result of the session creation * @param executor the executor to get the result of the session creation * @param callback the callback 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 SessionCallback#onSessionCreated(RouteSessionController, Bundle) * @see SessionCreationCallback#onSessionCreationFailed() * @see SessionCallback#onSessionCreationFailed() * * TODO: Separate callback registeration from creating session request. */ */ @NonNull @NonNull public void requestCreateSession(@NonNull MediaRoute2Info route, public void requestCreateSession(@NonNull MediaRoute2Info route, @NonNull String controlCategory, @NonNull String controlCategory, @CallbackExecutor Executor executor, @NonNull SessionCreationCallback callback) { @CallbackExecutor Executor executor, @NonNull SessionCallback callback) { Objects.requireNonNull(route, "route must not be null"); Objects.requireNonNull(route, "route must not be null"); if (TextUtils.isEmpty(controlCategory)) { if (TextUtils.isEmpty(controlCategory)) { throw new IllegalArgumentException("controlCategory must not be empty"); throw new IllegalArgumentException("controlCategory must not be empty"); Loading Loading @@ -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 * If session creation has failed, then it calls * {@link SessionCreationCallback#onSessionCreationFailed()}. * {@link SessionCallback#onSessionCreationFailed()}. * <p> * <p> * Pass {@code null} to sessionInfo for the failure case. * Pass {@code null} to sessionInfo for the failure case. */ */ Loading @@ -512,7 +514,7 @@ public class MediaRouter2 { mSessionCreationRequests.remove(matchingRequest); mSessionCreationRequests.remove(matchingRequest); final Executor executor = matchingRequest.mExecutor; final Executor executor = matchingRequest.mExecutor; final SessionCreationCallback callback = matchingRequest.mSessionCreationCallback; final SessionCallback callback = matchingRequest.mSessionCallback; if (sessionInfo == null) { if (sessionInfo == null) { // TODO: We may need to distinguish between failure and rejection. // TODO: We may need to distinguish between failure and rejection. Loading Loading @@ -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. * Called when the route session is created by the route provider. * * Loading @@ -594,6 +596,166 @@ public class MediaRouter2 { * Called when the session creation request failed. * Called when the session creation request failed. */ */ public void onSessionCreationFailed() {} 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 { final class RouteCallbackRecord { Loading @@ -616,8 +778,7 @@ public class MediaRouter2 { if (!(obj instanceof RouteCallbackRecord)) { if (!(obj instanceof RouteCallbackRecord)) { return false; return false; } } return mRouteCallback return mRouteCallback == ((RouteCallbackRecord) obj).mRouteCallback; == ((RouteCallbackRecord) obj).mRouteCallback; } } @Override @Override Loading @@ -630,17 +791,17 @@ public class MediaRouter2 { public final MediaRoute2Info mRoute; public final MediaRoute2Info mRoute; public final String mControlCategory; public final String mControlCategory; public final Executor mExecutor; public final Executor mExecutor; public final SessionCreationCallback mSessionCreationCallback; public final SessionCallback mSessionCallback; public final int mRequestId; public final int mRequestId; SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route, SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route, @NonNull String controlCategory, @NonNull String controlCategory, @Nullable Executor executor, @Nullable Executor executor, @NonNull SessionCreationCallback sessionCreationCallback) { @NonNull SessionCallback sessionCallback) { mRoute = route; mRoute = route; mControlCategory = controlCategory; mControlCategory = controlCategory; mExecutor = executor; mExecutor = executor; mSessionCreationCallback = sessionCreationCallback; mSessionCallback = sessionCallback; mRequestId = requestId; mRequestId = requestId; } } } } Loading media/java/android/media/RouteSessionController.javadeleted 100644 → 0 +0 −225 Original line number Original line 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; } } } media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java +6 −6 Original line number Original line Diff line number Diff line Loading @@ -43,8 +43,8 @@ import static org.testng.Assert.assertThrows; import android.content.Context; import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRoute2Info; import android.media.MediaRouter2; import android.media.MediaRouter2; import android.media.MediaRouter2.SessionCreationCallback; import android.media.MediaRouter2.RouteSessionController; import android.media.RouteSessionController; import android.media.MediaRouter2.SessionCallback; import android.net.Uri; import android.net.Uri; import android.os.Parcel; import android.os.Parcel; import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry; Loading Loading @@ -209,7 +209,7 @@ public class MediaRouter2Test { MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build(); MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build(); String controlCategory = "controlCategory"; String controlCategory = "controlCategory"; Executor executor = mExecutor; Executor executor = mExecutor; MediaRouter2.SessionCreationCallback callback = new MediaRouter2.SessionCreationCallback(); SessionCallback callback = new SessionCallback(); // Tests null route // Tests null route assertThrows(NullPointerException.class, assertThrows(NullPointerException.class, Loading Loading @@ -243,12 +243,12 @@ public class MediaRouter2Test { final CountDownLatch failureLatch = new CountDownLatch(1); final CountDownLatch failureLatch = new CountDownLatch(1); // Create session with this route // Create session with this route SessionCreationCallback callback = new SessionCreationCallback() { SessionCallback callback = new SessionCallback() { @Override @Override public void onSessionCreated(RouteSessionController controller) { public void onSessionCreated(RouteSessionController controller) { assertNotNull(controller); assertNotNull(controller); assertTrue(controller.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(controller.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getCategory())); assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory())); successLatch.countDown(); successLatch.countDown(); } } Loading Loading @@ -281,7 +281,7 @@ public class MediaRouter2Test { final CountDownLatch failureLatch = new CountDownLatch(1); final CountDownLatch failureLatch = new CountDownLatch(1); // Create session with this route // Create session with this route SessionCreationCallback callback = new SessionCreationCallback() { SessionCallback callback = new SessionCallback() { @Override @Override public void onSessionCreated(RouteSessionController controller) { public void onSessionCreated(RouteSessionController controller) { successLatch.countDown(); successLatch.countDown(); Loading Loading
media/java/android/media/MediaRouter2.java +174 −13 Original line number Original line Diff line number Diff line Loading @@ -291,13 +291,15 @@ public class MediaRouter2 { * @param executor the executor to get the result of the session creation * @param executor the executor to get the result of the session creation * @param callback the callback 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 SessionCallback#onSessionCreated(RouteSessionController, Bundle) * @see SessionCreationCallback#onSessionCreationFailed() * @see SessionCallback#onSessionCreationFailed() * * TODO: Separate callback registeration from creating session request. */ */ @NonNull @NonNull public void requestCreateSession(@NonNull MediaRoute2Info route, public void requestCreateSession(@NonNull MediaRoute2Info route, @NonNull String controlCategory, @NonNull String controlCategory, @CallbackExecutor Executor executor, @NonNull SessionCreationCallback callback) { @CallbackExecutor Executor executor, @NonNull SessionCallback callback) { Objects.requireNonNull(route, "route must not be null"); Objects.requireNonNull(route, "route must not be null"); if (TextUtils.isEmpty(controlCategory)) { if (TextUtils.isEmpty(controlCategory)) { throw new IllegalArgumentException("controlCategory must not be empty"); throw new IllegalArgumentException("controlCategory must not be empty"); Loading Loading @@ -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 * If session creation has failed, then it calls * {@link SessionCreationCallback#onSessionCreationFailed()}. * {@link SessionCallback#onSessionCreationFailed()}. * <p> * <p> * Pass {@code null} to sessionInfo for the failure case. * Pass {@code null} to sessionInfo for the failure case. */ */ Loading @@ -512,7 +514,7 @@ public class MediaRouter2 { mSessionCreationRequests.remove(matchingRequest); mSessionCreationRequests.remove(matchingRequest); final Executor executor = matchingRequest.mExecutor; final Executor executor = matchingRequest.mExecutor; final SessionCreationCallback callback = matchingRequest.mSessionCreationCallback; final SessionCallback callback = matchingRequest.mSessionCallback; if (sessionInfo == null) { if (sessionInfo == null) { // TODO: We may need to distinguish between failure and rejection. // TODO: We may need to distinguish between failure and rejection. Loading Loading @@ -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. * Called when the route session is created by the route provider. * * Loading @@ -594,6 +596,166 @@ public class MediaRouter2 { * Called when the session creation request failed. * Called when the session creation request failed. */ */ public void onSessionCreationFailed() {} 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 { final class RouteCallbackRecord { Loading @@ -616,8 +778,7 @@ public class MediaRouter2 { if (!(obj instanceof RouteCallbackRecord)) { if (!(obj instanceof RouteCallbackRecord)) { return false; return false; } } return mRouteCallback return mRouteCallback == ((RouteCallbackRecord) obj).mRouteCallback; == ((RouteCallbackRecord) obj).mRouteCallback; } } @Override @Override Loading @@ -630,17 +791,17 @@ public class MediaRouter2 { public final MediaRoute2Info mRoute; public final MediaRoute2Info mRoute; public final String mControlCategory; public final String mControlCategory; public final Executor mExecutor; public final Executor mExecutor; public final SessionCreationCallback mSessionCreationCallback; public final SessionCallback mSessionCallback; public final int mRequestId; public final int mRequestId; SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route, SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route, @NonNull String controlCategory, @NonNull String controlCategory, @Nullable Executor executor, @Nullable Executor executor, @NonNull SessionCreationCallback sessionCreationCallback) { @NonNull SessionCallback sessionCallback) { mRoute = route; mRoute = route; mControlCategory = controlCategory; mControlCategory = controlCategory; mExecutor = executor; mExecutor = executor; mSessionCreationCallback = sessionCreationCallback; mSessionCallback = sessionCallback; mRequestId = requestId; mRequestId = requestId; } } } } Loading
media/java/android/media/RouteSessionController.javadeleted 100644 → 0 +0 −225 Original line number Original line 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; } } }
media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java +6 −6 Original line number Original line Diff line number Diff line Loading @@ -43,8 +43,8 @@ import static org.testng.Assert.assertThrows; import android.content.Context; import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRoute2Info; import android.media.MediaRouter2; import android.media.MediaRouter2; import android.media.MediaRouter2.SessionCreationCallback; import android.media.MediaRouter2.RouteSessionController; import android.media.RouteSessionController; import android.media.MediaRouter2.SessionCallback; import android.net.Uri; import android.net.Uri; import android.os.Parcel; import android.os.Parcel; import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry; Loading Loading @@ -209,7 +209,7 @@ public class MediaRouter2Test { MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build(); MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build(); String controlCategory = "controlCategory"; String controlCategory = "controlCategory"; Executor executor = mExecutor; Executor executor = mExecutor; MediaRouter2.SessionCreationCallback callback = new MediaRouter2.SessionCreationCallback(); SessionCallback callback = new SessionCallback(); // Tests null route // Tests null route assertThrows(NullPointerException.class, assertThrows(NullPointerException.class, Loading Loading @@ -243,12 +243,12 @@ public class MediaRouter2Test { final CountDownLatch failureLatch = new CountDownLatch(1); final CountDownLatch failureLatch = new CountDownLatch(1); // Create session with this route // Create session with this route SessionCreationCallback callback = new SessionCreationCallback() { SessionCallback callback = new SessionCallback() { @Override @Override public void onSessionCreated(RouteSessionController controller) { public void onSessionCreated(RouteSessionController controller) { assertNotNull(controller); assertNotNull(controller); assertTrue(controller.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(controller.getSelectedRoutes().contains(ROUTE_ID1)); assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getCategory())); assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory())); successLatch.countDown(); successLatch.countDown(); } } Loading Loading @@ -281,7 +281,7 @@ public class MediaRouter2Test { final CountDownLatch failureLatch = new CountDownLatch(1); final CountDownLatch failureLatch = new CountDownLatch(1); // Create session with this route // Create session with this route SessionCreationCallback callback = new SessionCreationCallback() { SessionCallback callback = new SessionCallback() { @Override @Override public void onSessionCreated(RouteSessionController controller) { public void onSessionCreated(RouteSessionController controller) { successLatch.countDown(); successLatch.countDown(); Loading