Chromium Code Reviews| Index: chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CreateRouteRequest.java |
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CreateRouteRequest.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CreateRouteRequest.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..49cb8385c621fb970e258f108044f0c8929c5a19 |
| --- /dev/null |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CreateRouteRequest.java |
| @@ -0,0 +1,218 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +package org.chromium.chrome.browser.media.router.cast; |
| + |
| +import android.content.Context; |
| +import android.os.Bundle; |
| +import android.support.v7.media.MediaRouter; |
| + |
| +import com.google.android.gms.cast.Cast; |
| +import com.google.android.gms.cast.CastDevice; |
| +import com.google.android.gms.common.ConnectionResult; |
| +import com.google.android.gms.common.api.GoogleApiClient; |
| +import com.google.android.gms.common.api.PendingResult; |
| +import com.google.android.gms.common.api.ResultCallback; |
| +import com.google.android.gms.common.api.Status; |
| + |
| +import org.chromium.base.Log; |
| +import org.chromium.chrome.browser.media.router.ChromeMediaRouter; |
| + |
| +/** |
| + * Establishes a {@link MediaRoute} by starting a Cast application represented by the given |
| + * presentation URL. Reports success or failure to {@link ChromeMediaRouter}. |
| + * Since there're numerous asynchronous calls involved in getting the application to launch |
| + * the class is implemented as a state machine. |
| + */ |
| +public class CreateRouteRequest implements GoogleApiClient.ConnectionCallbacks, |
| + GoogleApiClient.OnConnectionFailedListener, |
| + ResultCallback<Cast.ApplicationConnectionResult> { |
| + private static final String TAG = "cr.MediaRouter"; |
| + |
| + private static final int STATE_IDLE = 0; |
| + private static final int STATE_CONNECTING_TO_API = 1; |
| + private static final int STATE_API_CONNECTION_SUSPENDED = 2; |
| + private static final int STATE_LAUNCHING_APPLICATION = 3; |
| + private static final int STATE_LAUNCH_SUCCEEDED = 4; |
| + private static final int STATE_TERMINATED = 5; |
| + |
| + private static final String ERROR_NEW_ROUTE_INVALID_SOURCE_URN = "Invalid source URN: %s"; |
| + private static final String ERROR_NEW_ROUTE_INVALID_SINK_URN = "Invalid sink URN: %s"; |
| + private static final String ERROR_NEW_ROUTE_LAUNCH_APPLICATION_FAILED = |
| + "Launch application failed: %s, %s"; |
| + private static final String ERROR_NEW_ROUTE_LAUNCH_APPLICATION_FAILED_STATUS = |
| + "Launch application failed with status: %s, %d, %s"; |
| + private static final String ERROR_NEW_ROUTE_CLIENT_CONNECTION_FAILED = |
| + "GoogleApiClient connection failed: %d, %b"; |
| + |
| + private final MediaSource mMediaSource; |
| + private final String mSinkId; |
| + private final String mPresentationId; |
| + private final int mRequestId; |
| + private final ChromeMediaRouter mMediaRouter; |
| + |
| + private GoogleApiClient mApiClient; |
| + private MediaRouter.RouteInfo mRoute; |
| + private int mState = STATE_IDLE; |
| + |
| + /** |
| + * Initializes the request. |
| + * @param sourceUrn The URN defining the application to launch on the Cast device |
| + * @param sinkUrn The URN identifying the selected Cast device |
| + * @param presentationId The presentation id assigned to the route by {@link ChromeMediaRouter} |
| + * @param requestId The id of the route creation request for tracking by |
| + * {@link ChromeMediaRouter} |
| + * @param router The instance of {@link ChromeMediaRouter} handling the request |
| + */ |
| + public CreateRouteRequest( |
| + String sourceUrn, |
| + String sinkUrn, |
| + String presentationId, |
| + int requestId, |
| + ChromeMediaRouter router) { |
| + mMediaSource = MediaSource.from(sourceUrn); |
| + mSinkId = sinkUrn; |
| + mPresentationId = presentationId; |
| + mRequestId = requestId; |
| + mMediaRouter = router; |
| + } |
| + |
| + /** |
| + * Starts the process of launching the application on the Cast device. |
| + * @param androidMediaRouter Android's {@link MediaRouter} instance. |
| + * @param applicationContext application context |
| + * @param castApplicationListener {@link com.google.android.gms.cast.Cast.Listener} |
| + * implementation provided by the caller. |
| + */ |
| + public void start( |
| + MediaRouter androidMediaRouter, |
| + Context applicationContext, |
| + Cast.Listener castApplicationListener) { |
| + assert androidMediaRouter != null; |
| + assert applicationContext != null; |
| + assert castApplicationListener != null; |
| + if (mState != STATE_IDLE) throwInvalidState(); |
|
mlamouri (slow - plz ping)
2015/08/18 16:04:54
nit: empty line after condition
mlamouri (slow - plz ping)
2015/08/18 16:04:55
nit: empty line after condition
What happens if s
whywhat
2015/08/18 20:06:32
Done.
PresentationServiceImpl will reject the cons
|
| + if (mMediaSource == null) { |
| + reportError(String.format(ERROR_NEW_ROUTE_INVALID_SOURCE_URN, mMediaSource.getUrn())); |
| + return; |
| + } |
| + mRoute = null; |
|
mlamouri (slow - plz ping)
2015/08/18 16:04:55
nit: empty line before mRoute
whywhat
2015/08/18 20:06:32
Done.
|
| + for (MediaRouter.RouteInfo route : androidMediaRouter.getRoutes()) { |
| + MediaSink routeSink = MediaSink.fromRoute(route); |
| + if (routeSink.getId().equals(mSinkId)) { |
| + mRoute = route; |
| + break; |
| + } |
| + } |
| + if (mRoute == null) { |
|
mlamouri (slow - plz ping)
2015/08/18 16:04:55
nit: empty line between for() and if()
whywhat
2015/08/18 20:06:32
Done.
|
| + reportError(String.format(ERROR_NEW_ROUTE_INVALID_SINK_URN, mSinkId)); |
| + return; |
| + } |
| + mApiClient = createApiClient(mRoute, castApplicationListener, applicationContext); |
|
mlamouri (slow - plz ping)
2015/08/18 16:04:55
nit: empty line after if()
whywhat
2015/08/18 20:06:32
Done.
|
| + mApiClient.connect(); |
| + mState = STATE_CONNECTING_TO_API; |
| + } |
| + |
| + @Override |
| + public void onConnected(Bundle connectionHint) { |
| + if (mState != STATE_CONNECTING_TO_API && mState != STATE_API_CONNECTION_SUSPENDED) { |
| + throwInvalidState(); |
| + } |
| + if (mState == STATE_API_CONNECTION_SUSPENDED) { |
| + // TODO(avayvod): figure out what needs to be reset at this point and reconnect |
|
mlamouri (slow - plz ping)
2015/08/18 16:04:55
nit: bug number
whywhat
2015/08/18 20:06:32
Updated the comment. We don't need to have all thi
|
| + return; |
| + } |
| + try { |
|
mlamouri (slow - plz ping)
2015/08/18 16:04:54
nit: empty line before try{}
whywhat
2015/08/18 20:06:32
Done.
|
| + launchApplication(mApiClient, mMediaSource.getApplicationId(), false) |
| + .setResultCallback(this); |
| + mState = STATE_LAUNCHING_APPLICATION; |
| + } catch (Exception e) { |
| + reportError(String.format(ERROR_NEW_ROUTE_LAUNCH_APPLICATION_FAILED, |
| + mMediaSource.getApplicationId(), e)); |
| + } |
| + } |
| + |
| + @Override |
| + public void onConnectionSuspended(int cause) { |
| + mState = STATE_API_CONNECTION_SUSPENDED; |
| + // TODO(avayvod): what to suspend here to be reconnected in {@link onConnected()}? |
|
mlamouri (slow - plz ping)
2015/08/18 16:04:55
nit: bug number?
whywhat
2015/08/18 20:06:32
Ditto
|
| + } |
| + |
| + @Override |
| + public void onResult(Cast.ApplicationConnectionResult result) { |
| + if (mState != STATE_LAUNCHING_APPLICATION) throwInvalidState(); |
| + Status status = result.getStatus(); |
|
mlamouri (slow - plz ping)
2015/08/18 16:04:55
nit: empty line after condition
whywhat
2015/08/18 20:06:32
Done.
|
| + if (status.isSuccess()) { |
|
mlamouri (slow - plz ping)
2015/08/18 16:04:54
nit:
if (!status.isSuccess()) {
reportError();
whywhat
2015/08/18 20:06:32
Done.
|
| + // TODO(avayvod): figure out if the commented info below is useful and should be kept. |
| + // String applicationStatus = result.getApplicationStatus(); |
| + // ApplicationMetadata applicationMetadata = |
| + // result.getApplicationMetadata(); |
|
mlamouri (slow - plz ping)
2015/08/18 16:04:55
shrug...
whywhat
2015/08/18 20:06:32
Removed.
|
| + String sessionId = result.getSessionId(); |
| + boolean wasLaunched = result.getWasLaunched(); |
| + mState = STATE_LAUNCH_SUCCEEDED; |
| + reportSuccess(sessionId, wasLaunched); |
| + } else { |
| + reportError(String.format( |
| + ERROR_NEW_ROUTE_LAUNCH_APPLICATION_FAILED_STATUS, |
| + mMediaSource.getApplicationId(), |
| + status.getStatusCode(), |
| + status.getStatusMessage())); |
| + } |
| + } |
| + |
| + @Override |
| + public void onConnectionFailed(ConnectionResult result) { |
| + if (mState != STATE_CONNECTING_TO_API) throwInvalidState(); |
|
mlamouri (slow - plz ping)
2015/08/18 16:04:54
nit: empty line after condition
whywhat
2015/08/18 20:06:32
Done.
|
| + // TODO(avayvod): figure out how to extend GoogleApiClientConnectionHelper here and |
| + // try to resolve the problem (e.g. ask the user to update GPS). |
|
mlamouri (slow - plz ping)
2015/08/18 16:04:55
nit: add bug number.
whywhat
2015/08/18 20:06:32
Ditto. Hopefully will remove altogether.
|
| + reportError(String.format( |
| + ERROR_NEW_ROUTE_CLIENT_CONNECTION_FAILED, |
| + result.getErrorCode(), |
| + result.hasResolution())); |
| + } |
| + |
| + private GoogleApiClient createApiClient( |
| + MediaRouter.RouteInfo route, Cast.Listener listener, Context context) { |
| + CastDevice selectedDevice = CastDevice.getFromBundle(route.getExtras()); |
| + Cast.CastOptions.Builder apiOptionsBuilder = Cast.CastOptions |
| + .builder(selectedDevice, listener) |
| + // TODO(avayvod): hide this behind the flag or remove |
| + .setVerboseLoggingEnabled(true); |
| + return new GoogleApiClient.Builder(context) |
| + .addApi(Cast.API, apiOptionsBuilder.build()) |
| + .addConnectionCallbacks(this) |
| + .addOnConnectionFailedListener(this) |
| + .build(); |
| + } |
| + |
| + private PendingResult<Cast.ApplicationConnectionResult> launchApplication( |
| + GoogleApiClient apiClient, |
| + String appId, |
| + boolean relaunchIfRunning) { |
| + return Cast.CastApi.launchApplication(apiClient, appId, relaunchIfRunning); |
| + } |
| + |
| + private void throwInvalidState() { |
| + Log.e(TAG, "throwInvalidState: %d", mState); |
| + throw new RuntimeException(String.format("Invalid state: %d", mState)); |
| + } |
| + |
| + private void reportSuccess(String sessionId, boolean wasLaunched) { |
| + if (mState != STATE_LAUNCH_SUCCEEDED) throwInvalidState(); |
| + |
| + String mediaRouteId = String.format( |
| + "route:%s/%s/%s", mPresentationId, mSinkId, mMediaSource.getUrn()); |
| + mMediaRouter.onRouteCreated(mediaRouteId, mRequestId, sessionId, wasLaunched); |
| + mState = STATE_TERMINATED; |
| + } |
| + |
| + private void reportError(String message) { |
| + Log.e(TAG, "reportError: %s", message); |
| + if (mState == STATE_TERMINATED) throwInvalidState(); |
| + assert mMediaRouter != null; |
| + mMediaRouter.onRouteCreationError(message, mRequestId); |
| + mState = STATE_TERMINATED; |
| + } |
| + |
| +} |