diff options
6 files changed, 241 insertions, 66 deletions
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialog.java b/core/java/com/android/internal/app/MediaRouteChooserDialog.java index b963c74..944cc83 100644 --- a/core/java/com/android/internal/app/MediaRouteChooserDialog.java +++ b/core/java/com/android/internal/app/MediaRouteChooserDialog.java @@ -106,8 +106,8 @@ public class MediaRouteChooserDialog extends Dialog { /** * Returns true if the route should be included in the list. * <p> - * The default implementation returns true for non-default routes that - * match the selector. Subclasses can override this method to filter routes + * The default implementation returns true for enabled non-default routes that + * match the route types. Subclasses can override this method to filter routes * differently. * </p> * @@ -115,7 +115,7 @@ public class MediaRouteChooserDialog extends Dialog { * @return True if the route should be included in the chooser dialog. */ public boolean onFilterRoute(MediaRouter.RouteInfo route) { - return !route.isDefault() && route.matchesTypes(mRouteTypes); + return !route.isDefault() && route.isEnabled() && route.matchesTypes(mRouteTypes); } @Override diff --git a/core/res/res/layout/media_route_chooser_dialog.xml b/core/res/res/layout/media_route_chooser_dialog.xml index 3eba9be..d1c6267 100644 --- a/core/res/res/layout/media_route_chooser_dialog.xml +++ b/core/res/res/layout/media_route_chooser_dialog.xml @@ -23,7 +23,8 @@ <!-- List of routes. --> <ListView android:id="@+id/media_route_list" android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + android:layout_weight="1" /> <!-- Content to show when list is empty. --> <LinearLayout android:id="@android:id/empty" diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index fb753c5..3d10158 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -469,7 +469,7 @@ public class MediaRouter { route.mDescription = globalRoute.description; route.mSupportedTypes = globalRoute.supportedTypes; route.mEnabled = globalRoute.enabled; - route.setStatusCode(globalRoute.statusCode); + route.setRealStatusCode(globalRoute.statusCode); route.mPlaybackType = globalRoute.playbackType; route.mPlaybackStream = globalRoute.playbackStream; route.mVolume = globalRoute.volume; @@ -501,8 +501,8 @@ public class MediaRouter { route.mEnabled = globalRoute.enabled; changed = true; } - if (route.mStatusCode != globalRoute.statusCode) { - route.setStatusCode(globalRoute.statusCode); + if (route.mRealStatusCode != globalRoute.statusCode) { + route.setRealStatusCode(globalRoute.statusCode); changed = true; } if (route.mPlaybackType != globalRoute.playbackType) { @@ -918,8 +918,14 @@ public class MediaRouter { if (oldRoute != null) { dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute); + if (oldRoute.resolveStatusCode()) { + dispatchRouteChanged(oldRoute); + } } if (route != null) { + if (route.resolveStatusCode()) { + dispatchRouteChanged(route); + } dispatchRouteSelected(types & route.getSupportedTypes(), route); } } @@ -1337,7 +1343,7 @@ public class MediaRouter { newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED; newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE; - newRoute.setStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); + newRoute.setRealStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); newRoute.mEnabled = isWifiDisplayEnabled(display, wfdStatus); newRoute.mName = display.getFriendlyDisplayName(); newRoute.mDescription = sStatic.mResources.getText( @@ -1359,7 +1365,7 @@ public class MediaRouter { changed |= route.mEnabled != enabled; route.mEnabled = enabled; - changed |= route.setStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); + changed |= route.setRealStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); if (changed) { dispatchRouteChanged(route); @@ -1422,7 +1428,8 @@ public class MediaRouter { String mGlobalRouteId; // A predetermined connection status that can override mStatus - private int mStatusCode; + private int mRealStatusCode; + private int mResolvedStatusCode; /** @hide */ public static final int STATUS_NONE = 0; /** @hide */ public static final int STATUS_SCANNING = 1; @@ -1526,43 +1533,71 @@ public class MediaRouter { * Set this route's status by predetermined status code. If the caller * should dispatch a route changed event this call will return true; */ - boolean setStatusCode(int statusCode) { - if (statusCode != mStatusCode) { - mStatusCode = statusCode; - int resId; + boolean setRealStatusCode(int statusCode) { + if (mRealStatusCode != statusCode) { + mRealStatusCode = statusCode; + return resolveStatusCode(); + } + return false; + } + + /** + * Resolves the status code whenever the real status code or selection state + * changes. + */ + boolean resolveStatusCode() { + int statusCode = mRealStatusCode; + if (isSelected()) { switch (statusCode) { - case STATUS_SCANNING: - resId = com.android.internal.R.string.media_route_status_scanning; - break; - case STATUS_CONNECTING: - resId = com.android.internal.R.string.media_route_status_connecting; - break; + // If the route is selected and its status appears to be between states + // then report it as connecting even though it has not yet had a chance + // to officially move into the CONNECTING state. Note that routes in + // the NONE state are assumed to not require an explicit connection + // lifecycle whereas those that are AVAILABLE are assumed to have + // to eventually proceed to CONNECTED. case STATUS_AVAILABLE: - resId = com.android.internal.R.string.media_route_status_available; - break; - case STATUS_NOT_AVAILABLE: - resId = com.android.internal.R.string.media_route_status_not_available; - break; - case STATUS_IN_USE: - resId = com.android.internal.R.string.media_route_status_in_use; - break; - case STATUS_CONNECTED: - case STATUS_NONE: - default: - resId = 0; + case STATUS_SCANNING: + statusCode = STATUS_CONNECTING; break; } - mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null; - return true; } - return false; + if (mResolvedStatusCode == statusCode) { + return false; + } + + mResolvedStatusCode = statusCode; + int resId; + switch (statusCode) { + case STATUS_SCANNING: + resId = com.android.internal.R.string.media_route_status_scanning; + break; + case STATUS_CONNECTING: + resId = com.android.internal.R.string.media_route_status_connecting; + break; + case STATUS_AVAILABLE: + resId = com.android.internal.R.string.media_route_status_available; + break; + case STATUS_NOT_AVAILABLE: + resId = com.android.internal.R.string.media_route_status_not_available; + break; + case STATUS_IN_USE: + resId = com.android.internal.R.string.media_route_status_in_use; + break; + case STATUS_CONNECTED: + case STATUS_NONE: + default: + resId = 0; + break; + } + mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null; + return true; } /** * @hide */ public int getStatusCode() { - return mStatusCode; + return mResolvedStatusCode; } /** @@ -1821,19 +1856,7 @@ public class MediaRouter { * @return True if this route is in the process of connecting. */ public boolean isConnecting() { - // If the route is selected and its status appears to be between states - // then report it as connecting even though it has not yet had a chance - // to move into the CONNECTING state. Note that routes in the NONE state - // are assumed to not require an explicit connection lifecycle. - if (isSelected()) { - switch (mStatusCode) { - case STATUS_AVAILABLE: - case STATUS_SCANNING: - case STATUS_CONNECTING: - return true; - } - } - return false; + return mResolvedStatusCode == STATUS_CONNECTING; } /** @hide */ diff --git a/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java b/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java index 701c39c..e2df77c 100644 --- a/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java +++ b/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java @@ -16,6 +16,7 @@ package com.android.media.remotedisplay; +import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; @@ -27,6 +28,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.provider.Settings; import android.util.ArrayMap; import java.util.Collection; @@ -95,6 +97,7 @@ public abstract class RemoteDisplayProvider { private static final int MSG_SET_VOLUME = 5; private static final int MSG_ADJUST_VOLUME = 6; + private final Context mContext; private final ProviderStub mStub; private final ProviderHandler mHandler; private final ArrayMap<String, RemoteDisplay> mDisplays = @@ -102,6 +105,8 @@ public abstract class RemoteDisplayProvider { private IRemoteDisplayCallback mCallback; private int mDiscoveryMode = DISCOVERY_MODE_NONE; + private PendingIntent mSettingsPendingIntent; + /** * The {@link Intent} that must be declared as handled by the service. * Put this in your manifest. @@ -140,11 +145,19 @@ public abstract class RemoteDisplayProvider { * @param context The application context for the remote display provider. */ public RemoteDisplayProvider(Context context) { + mContext = context; mStub = new ProviderStub(); mHandler = new ProviderHandler(context.getMainLooper()); } /** + * Gets the context of the remote display provider. + */ + public final Context getContext() { + return mContext; + } + + /** * Gets the Binder associated with the provider. * <p> * This is intended to be used for the onBind() method of a service that implements @@ -261,11 +274,29 @@ public abstract class RemoteDisplayProvider { * Finds the remote display with the specified id, returns null if not found. * * @param id Id of the remote display. + * @return The display, or null if none. */ public RemoteDisplay findRemoteDisplay(String id) { return mDisplays.get(id); } + /** + * Gets a pending intent to launch the remote display settings activity. + * + * @return A pending intent to launch the settings activity. + */ + public PendingIntent getSettingsPendingIntent() { + if (mSettingsPendingIntent == null) { + Intent settingsIntent = new Intent(Settings.ACTION_WIFI_DISPLAY_SETTINGS); + settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mSettingsPendingIntent = PendingIntent.getActivity( + mContext, 0, settingsIntent, 0, null); + } + return mSettingsPendingIntent; + } + void setCallback(IRemoteDisplayCallback callback) { mCallback = callback; publishState(); diff --git a/services/java/com/android/server/media/MediaRouterService.java b/services/java/com/android/server/media/MediaRouterService.java index 2caab40..1491eb6 100644 --- a/services/java/com/android/server/media/MediaRouterService.java +++ b/services/java/com/android/server/media/MediaRouterService.java @@ -600,8 +600,16 @@ public final class MediaRouterService extends IMediaRouterService.Stub private static final int MSG_CONNECTION_TIMED_OUT = 9; private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1; - private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 2; - private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 3; + private static final int TIMEOUT_REASON_CONNECTION_LOST = 2; + private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3; + private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4; + + // The relative order of these constants is important and expresses progress + // through the process of connecting to a route. + private static final int PHASE_NOT_AVAILABLE = -1; + private static final int PHASE_NOT_CONNECTED = 0; + private static final int PHASE_CONNECTING = 1; + private static final int PHASE_CONNECTED = 2; private final MediaRouterService mService; private final UserRecord mUserRecord; @@ -614,6 +622,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub private boolean mRunning; private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE; private RouteRecord mGloballySelectedRouteRecord; + private int mConnectionPhase = PHASE_NOT_AVAILABLE; private int mConnectionTimeoutReason; private long mConnectionTimeoutStartTime; private boolean mClientStateUpdateScheduled; @@ -675,6 +684,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub pw.println(indent + "mRunning=" + mRunning); pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode); pw.println(indent + "mGloballySelectedRouteRecord=" + mGloballySelectedRouteRecord); + pw.println(indent + "mConnectionPhase=" + mConnectionPhase); pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason); pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ? TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>")); @@ -843,6 +853,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub private void checkGloballySelectedRouteState() { // Unschedule timeouts when the route is unselected. if (mGloballySelectedRouteRecord == null) { + mConnectionPhase = PHASE_NOT_AVAILABLE; updateConnectionTimeout(0); return; } @@ -854,29 +865,34 @@ public final class MediaRouterService extends IMediaRouterService.Stub return; } + // Make sure we haven't lost our connection. + final int oldPhase = mConnectionPhase; + mConnectionPhase = getConnectionPhase(mGloballySelectedRouteRecord.getStatus()); + if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) { + updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST); + return; + } + // Check the route status. - switch (mGloballySelectedRouteRecord.getStatus()) { - case MediaRouter.RouteInfo.STATUS_NONE: - case MediaRouter.RouteInfo.STATUS_CONNECTED: - if (mConnectionTimeoutReason != 0) { + switch (mConnectionPhase) { + case PHASE_CONNECTED: + if (oldPhase != PHASE_CONNECTED) { Slog.i(TAG, "Connected to global route: " + mGloballySelectedRouteRecord); } updateConnectionTimeout(0); break; - case MediaRouter.RouteInfo.STATUS_CONNECTING: - if (mConnectionTimeoutReason != 0) { + case PHASE_CONNECTING: + if (oldPhase != PHASE_CONNECTING) { Slog.i(TAG, "Connecting to global route: " + mGloballySelectedRouteRecord); } updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED); break; - case MediaRouter.RouteInfo.STATUS_SCANNING: - case MediaRouter.RouteInfo.STATUS_AVAILABLE: + case PHASE_NOT_CONNECTED: updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING); break; - case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE: - case MediaRouter.RouteInfo.STATUS_IN_USE: + case PHASE_NOT_AVAILABLE: default: updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE); break; @@ -892,7 +908,9 @@ public final class MediaRouterService extends IMediaRouterService.Stub mConnectionTimeoutStartTime = SystemClock.uptimeMillis(); switch (reason) { case TIMEOUT_REASON_NOT_AVAILABLE: - // Route became unavailable. Unselect it immediately. + case TIMEOUT_REASON_CONNECTION_LOST: + // Route became unavailable or connection lost. + // Unselect it immediately. sendEmptyMessage(MSG_CONNECTION_TIMED_OUT); break; case TIMEOUT_REASON_WAITING_FOR_CONNECTING: @@ -919,6 +937,10 @@ public final class MediaRouterService extends IMediaRouterService.Stub Slog.i(TAG, "Global route no longer available: " + mGloballySelectedRouteRecord); break; + case TIMEOUT_REASON_CONNECTION_LOST: + Slog.i(TAG, "Global route connection lost: " + + mGloballySelectedRouteRecord); + break; case TIMEOUT_REASON_WAITING_FOR_CONNECTING: Slog.i(TAG, "Global route timed out while waiting for " + "connection attempt to begin after " @@ -1004,6 +1026,23 @@ public final class MediaRouterService extends IMediaRouterService.Stub return null; } + private static int getConnectionPhase(int status) { + switch (status) { + case MediaRouter.RouteInfo.STATUS_NONE: + case MediaRouter.RouteInfo.STATUS_CONNECTED: + return PHASE_CONNECTED; + case MediaRouter.RouteInfo.STATUS_CONNECTING: + return PHASE_CONNECTING; + case MediaRouter.RouteInfo.STATUS_SCANNING: + case MediaRouter.RouteInfo.STATUS_AVAILABLE: + return PHASE_NOT_CONNECTED; + case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE: + case MediaRouter.RouteInfo.STATUS_IN_USE: + default: + return PHASE_NOT_AVAILABLE; + } + } + static final class ProviderRecord { private final RemoteDisplayProviderProxy mProvider; private final String mUniquePrefix; diff --git a/tests/RemoteDisplayProvider/src/com/android/media/remotedisplay/test/RemoteDisplayProviderService.java b/tests/RemoteDisplayProvider/src/com/android/media/remotedisplay/test/RemoteDisplayProviderService.java index bf84631..611d7e4 100644 --- a/tests/RemoteDisplayProvider/src/com/android/media/remotedisplay/test/RemoteDisplayProviderService.java +++ b/tests/RemoteDisplayProvider/src/com/android/media/remotedisplay/test/RemoteDisplayProviderService.java @@ -52,6 +52,9 @@ public class RemoteDisplayProviderService extends Service { private RemoteDisplay mTestDisplay5; // available but ignores request to connect private RemoteDisplay mTestDisplay6; // available but never finishes connecting private RemoteDisplay mTestDisplay7; // blinks in and out of existence + private RemoteDisplay mTestDisplay8; // available but connecting attempt flakes out + private RemoteDisplay mTestDisplay9; // available but connection flakes out + private RemoteDisplay mTestDisplay10; // available and reconnects periodically private final Handler mHandler; private boolean mBlinking; @@ -112,6 +115,27 @@ public class RemoteDisplayProviderService extends Service { mTestDisplay6.setStatus(RemoteDisplay.STATUS_AVAILABLE); addDisplay(mTestDisplay6); } + if (mTestDisplay8 == null) { + mTestDisplay8 = new RemoteDisplay("testDisplay8", + "Test Display 8 (flaky when connecting)"); + mTestDisplay8.setDescription("Aborts spontaneously while connecting"); + mTestDisplay8.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay8); + } + if (mTestDisplay9 == null) { + mTestDisplay9 = new RemoteDisplay("testDisplay9", + "Test Display 9 (flaky when connected)"); + mTestDisplay9.setDescription("Aborts spontaneously while connected"); + mTestDisplay9.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay9); + } + if (mTestDisplay10 == null) { + mTestDisplay10 = new RemoteDisplay("testDisplay10", + "Test Display 10 (reconnects periodically)"); + mTestDisplay10.setDescription("Reconnects spontaneously"); + mTestDisplay10.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay10); + } } else { // When discovery ends, go hide some of the routes we can't actually use. // This isn't something a normal route provider would do though. @@ -144,6 +168,7 @@ public class RemoteDisplayProviderService extends Service { if (display == mTestDisplay1 || display == mTestDisplay2) { display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); mHandler.postDelayed(new Runnable() { @Override public void run() { @@ -154,12 +179,67 @@ public class RemoteDisplayProviderService extends Service { } } }, 2000); - updateDisplay(display); - } - if (display == mTestDisplay6 || display == mTestDisplay7) { + } else if (display == mTestDisplay6 || display == mTestDisplay7) { // never finishes connecting display.setStatus(RemoteDisplay.STATUS_CONNECTING); updateDisplay(display); + } else if (display == mTestDisplay8) { + // flakes out while connecting + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if ((display == mTestDisplay8) + && display.getStatus() == RemoteDisplay.STATUS_CONNECTING) { + display.setStatus(RemoteDisplay.STATUS_AVAILABLE); + updateDisplay(display); + } + } + }, 2000); + } else if (display == mTestDisplay9) { + // flakes out when connected + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if ((display == mTestDisplay9) + && display.getStatus() == RemoteDisplay.STATUS_CONNECTING) { + display.setStatus(RemoteDisplay.STATUS_CONNECTED); + updateDisplay(display); + } + } + }, 2000); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if ((display == mTestDisplay9) + && display.getStatus() == RemoteDisplay.STATUS_CONNECTED) { + display.setStatus(RemoteDisplay.STATUS_AVAILABLE); + updateDisplay(display); + } + } + }, 5000); + } else if (display == mTestDisplay10) { + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (display == mTestDisplay10) { + if (display.getStatus() == RemoteDisplay.STATUS_CONNECTING) { + display.setStatus(RemoteDisplay.STATUS_CONNECTED); + updateDisplay(display); + mHandler.postDelayed(this, 7000); + } else if (display.getStatus() == RemoteDisplay.STATUS_CONNECTED) { + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + mHandler.postDelayed(this, 2000); + } + } + } + }, 2000); } } @@ -168,7 +248,8 @@ public class RemoteDisplayProviderService extends Service { Log.d(TAG, "onDisconnect: display.getId()=" + display.getId()); if (display == mTestDisplay1 || display == mTestDisplay2 - || display == mTestDisplay6) { + || display == mTestDisplay6 || display == mTestDisplay8 + || display == mTestDisplay9 || display == mTestDisplay10) { display.setStatus(RemoteDisplay.STATUS_AVAILABLE); updateDisplay(display); } |