Loading core/java/android/app/MediaRouteActionProvider.java +92 −78 Original line number Diff line number Diff line Loading @@ -16,10 +16,7 @@ package android.app; import com.android.internal.app.MediaRouteChooserDialogFragment; import android.content.Context; import android.content.ContextWrapper; import android.media.MediaRouter; import android.media.MediaRouter.RouteInfo; import android.util.Log; Loading @@ -30,22 +27,38 @@ import android.view.ViewGroup; import java.lang.ref.WeakReference; /** * The media route action provider displays a {@link MediaRouteButton media route button} * in the application's {@link ActionBar} to allow the user to select routes and * to control the currently selected route. * <p> * The application must specify the kinds of routes that the user should be allowed * to select by specifying the route types with the {@link #setRouteTypes} method. * </p><p> * Refer to {@link MediaRouteButton} for a description of the button that will * appear in the action bar menu. Note that instead of disabling the button * when no routes are available, the action provider will instead make the * menu item invisible. In this way, the button will only be visible when it * is possible for the user to discover and select a matching route. * </p> */ public class MediaRouteActionProvider extends ActionProvider { private static final String TAG = "MediaRouteActionProvider"; private Context mContext; private MediaRouter mRouter; private MenuItem mMenuItem; private MediaRouteButton mView; private final Context mContext; private final MediaRouter mRouter; private final MediaRouterCallback mCallback; private int mRouteTypes; private MediaRouteButton mButton; private View.OnClickListener mExtendedSettingsListener; private RouterCallback mCallback; public MediaRouteActionProvider(Context context) { super(context); mContext = context; mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); mCallback = new RouterCallback(this); mCallback = new MediaRouterCallback(this); // Start with live audio by default. // TODO Update this when new route types are added; segment by API level Loading @@ -53,80 +66,74 @@ public class MediaRouteActionProvider extends ActionProvider { setRouteTypes(MediaRouter.ROUTE_TYPE_LIVE_AUDIO); } /** * Sets the types of routes that will be shown in the media route chooser dialog * launched by this button. * * @param types The route types to match. */ public void setRouteTypes(int types) { if (mRouteTypes == types) return; if (mRouteTypes != types) { // FIXME: We currently have no way of knowing whether the action provider // is still needed by the UI. Unfortunately this means the action provider // may leak callbacks until garbage collection occurs. This may result in // media route providers doing more work than necessary in the short term // while trying to discover routes that are no longer of interest to the // application. To solve this problem, the action provider will need some // indication from the framework that it is being destroyed. if (mRouteTypes != 0) { mRouter.removeCallback(mCallback); } mRouteTypes = types; if (types != 0) { mRouter.addCallback(types, mCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); mRouter.addCallback(types, mCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); } refreshRoute(); if (mButton != null) { mButton.setRouteTypes(mRouteTypes); } if (mView != null) { mView.setRouteTypes(mRouteTypes); } } public void setExtendedSettingsClickListener(View.OnClickListener listener) { mExtendedSettingsListener = listener; if (mButton != null) { mButton.setExtendedSettingsClickListener(listener); } } @Override @SuppressWarnings("deprecation") public View onCreateActionView() { throw new UnsupportedOperationException("Use onCreateActionView(MenuItem) instead."); } @Override public View onCreateActionView(MenuItem item) { if (mMenuItem != null || mView != null) { if (mButton != null) { Log.e(TAG, "onCreateActionView: this ActionProvider is already associated " + "with a menu item. Don't reuse MediaRouteActionProvider instances! " + "Abandoning the old one..."); } mMenuItem = item; mView = new MediaRouteButton(mContext); mView.setCheatSheetEnabled(true); mView.setRouteTypes(mRouteTypes); mView.setExtendedSettingsClickListener(mExtendedSettingsListener); mView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mButton = new MediaRouteButton(mContext); mButton.setCheatSheetEnabled(true); mButton.setRouteTypes(mRouteTypes); mButton.setExtendedSettingsClickListener(mExtendedSettingsListener); mButton.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); return mView; return mButton; } @Override public boolean onPerformDefaultAction() { final FragmentManager fm = getActivity().getFragmentManager(); // See if one is already attached to this activity. MediaRouteChooserDialogFragment dialogFragment = (MediaRouteChooserDialogFragment) fm.findFragmentByTag( MediaRouteChooserDialogFragment.FRAGMENT_TAG); if (dialogFragment != null) { Log.w(TAG, "onPerformDefaultAction(): Chooser dialog already showing!"); return false; } dialogFragment = new MediaRouteChooserDialogFragment(); dialogFragment.setExtendedSettingsClickListener(mExtendedSettingsListener); dialogFragment.setRouteTypes(mRouteTypes); dialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG); return true; } private Activity getActivity() { // Gross way of unwrapping the Activity so we can get the FragmentManager Context context = mContext; while (context instanceof ContextWrapper && !(context instanceof Activity)) { context = ((ContextWrapper) context).getBaseContext(); } if (!(context instanceof Activity)) { throw new IllegalStateException("The MediaRouteActionProvider's Context " + "is not an Activity."); } return (Activity) context; } public void setExtendedSettingsClickListener(View.OnClickListener listener) { mExtendedSettingsListener = listener; if (mView != null) { mView.setExtendedSettingsClickListener(listener); if (mButton != null) { return mButton.showDialogInternal(); } return false; } @Override Loading @@ -136,36 +143,43 @@ public class MediaRouteActionProvider extends ActionProvider { @Override public boolean isVisible() { return mRouter.getRouteCount() > 1; return mRouter.isRouteAvailable(mRouteTypes, MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE); } private static class RouterCallback extends MediaRouter.SimpleCallback { private WeakReference<MediaRouteActionProvider> mAp; private void refreshRoute() { refreshVisibility(); } private static class MediaRouterCallback extends MediaRouter.SimpleCallback { private final WeakReference<MediaRouteActionProvider> mProviderWeak; RouterCallback(MediaRouteActionProvider ap) { mAp = new WeakReference<MediaRouteActionProvider>(ap); public MediaRouterCallback(MediaRouteActionProvider provider) { mProviderWeak = new WeakReference<MediaRouteActionProvider>(provider); } @Override public void onRouteAdded(MediaRouter router, RouteInfo info) { final MediaRouteActionProvider ap = mAp.get(); if (ap == null) { router.removeCallback(this); return; refreshRoute(router); } ap.refreshVisibility(); @Override public void onRouteRemoved(MediaRouter router, RouteInfo info) { refreshRoute(router); } @Override public void onRouteRemoved(MediaRouter router, RouteInfo info) { final MediaRouteActionProvider ap = mAp.get(); if (ap == null) { router.removeCallback(this); return; public void onRouteChanged(MediaRouter router, RouteInfo info) { refreshRoute(router); } ap.refreshVisibility(); private void refreshRoute(MediaRouter router) { MediaRouteActionProvider provider = mProviderWeak.get(); if (provider != null) { provider.refreshRoute(); } else { router.removeCallback(this); } } } } core/java/android/app/MediaRouteButton.java +144 −178 Original line number Diff line number Diff line Loading @@ -17,7 +17,7 @@ package android.app; import com.android.internal.R; import com.android.internal.app.MediaRouteChooserDialogFragment; import com.android.internal.app.MediaRouteDialogPresenter; import android.content.Context; import android.content.ContextWrapper; Loading @@ -30,7 +30,6 @@ import android.media.MediaRouter.RouteGroup; import android.media.MediaRouter.RouteInfo; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.SoundEffectConstants; Loading @@ -38,17 +37,15 @@ import android.view.View; import android.widget.Toast; public class MediaRouteButton extends View { private static final String TAG = "MediaRouteButton"; private final MediaRouter mRouter; private final MediaRouterCallback mCallback; private MediaRouter mRouter; private final MediaRouteCallback mRouterCallback = new MediaRouteCallback(); private int mRouteTypes; private boolean mAttachedToWindow; private Drawable mRemoteIndicator; private boolean mRemoteActive; private boolean mToggleMode; private boolean mCheatSheetEnabled; private boolean mIsConnecting; Loading @@ -56,12 +53,13 @@ public class MediaRouteButton extends View { private int mMinHeight; private OnClickListener mExtendedSettingsClickListener; private MediaRouteChooserDialogFragment mDialogFragment; // The checked state is used when connected to a remote route. private static final int[] CHECKED_STATE_SET = { R.attr.state_checked }; // The activated state is used while connecting to a remote route. private static final int[] ACTIVATED_STATE_SET = { R.attr.state_activated }; Loading @@ -78,6 +76,7 @@ public class MediaRouteButton extends View { super(context, attrs, defStyleAttr); mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE); mCallback = new MediaRouterCallback(); TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, 0); Loading @@ -98,53 +97,99 @@ public class MediaRouteButton extends View { setRouteTypes(routeTypes); } private void setRemoteIndicatorDrawable(Drawable d) { if (mRemoteIndicator != null) { mRemoteIndicator.setCallback(null); unscheduleDrawable(mRemoteIndicator); /** * Gets the media route types for filtering the routes that the user can * select using the media route chooser dialog. * * @return The route types. */ public int getRouteTypes() { return mRouteTypes; } mRemoteIndicator = d; if (d != null) { d.setCallback(this); d.setState(getDrawableState()); d.setVisible(getVisibility() == VISIBLE, false); /** * Sets the types of routes that will be shown in the media route chooser dialog * launched by this button. * * @param types The route types to match. */ public void setRouteTypes(int types) { if (mRouteTypes != types) { if (mAttachedToWindow && mRouteTypes != 0) { mRouter.removeCallback(mCallback); } refreshDrawableState(); mRouteTypes = types; if (mAttachedToWindow && types != 0) { mRouter.addCallback(types, mCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); } @Override public boolean performClick() { // Send the appropriate accessibility events and call listeners boolean handled = super.performClick(); if (!handled) { playSoundEffect(SoundEffectConstants.CLICK); refreshRoute(); } } if (mToggleMode) { if (mRemoteActive) { mRouter.selectRouteInt(mRouteTypes, mRouter.getDefaultRoute(), true); } else { final int N = mRouter.getRouteCount(); for (int i = 0; i < N; i++) { final RouteInfo route = mRouter.getRouteAt(i); if ((route.getSupportedTypes() & mRouteTypes) != 0 && route != mRouter.getDefaultRoute()) { mRouter.selectRouteInt(mRouteTypes, route, true); public void setExtendedSettingsClickListener(OnClickListener listener) { mExtendedSettingsClickListener = listener; } /** * Show the route chooser or controller dialog. * <p> * If the default route is selected or if the currently selected route does * not match the {@link #getRouteTypes route types}, then shows the route chooser dialog. * Otherwise, shows the route controller dialog to offer the user * a choice to disconnect from the route or perform other control actions * such as setting the route's volume. * </p><p> * This will attach a {@link DialogFragment} to the containing Activity. * </p> */ public void showDialog() { showDialogInternal(); } boolean showDialogInternal() { if (!mAttachedToWindow) { return false; } } else { showDialog(); DialogFragment f = MediaRouteDialogPresenter.showDialogFragment(getActivity(), mRouteTypes, mExtendedSettingsClickListener); return f != null; } return handled; private Activity getActivity() { // Gross way of unwrapping the Activity so we can get the FragmentManager Context context = getContext(); while (context instanceof ContextWrapper) { if (context instanceof Activity) { return (Activity)context; } context = ((ContextWrapper)context).getBaseContext(); } throw new IllegalStateException("The MediaRouteButton's Context is not an Activity."); } /** * Sets whether to enable showing a toast with the content descriptor of the * button when the button is long pressed. */ void setCheatSheetEnabled(boolean enable) { mCheatSheetEnabled = enable; } @Override public boolean performClick() { // Send the appropriate accessibility events and call listeners boolean handled = super.performClick(); if (!handled) { playSoundEffect(SoundEffectConstants.CLICK); } return showDialogInternal() || handled; } @Override public boolean performLongClick() { if (super.performLongClick()) { Loading Loading @@ -183,87 +228,9 @@ public class MediaRouteButton extends View { } cheatSheet.show(); performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); return true; } public void setRouteTypes(int types) { if (types == mRouteTypes) { // Already registered; nothing to do. return; } if (mAttachedToWindow && mRouteTypes != 0) { mRouter.removeCallback(mRouterCallback); } mRouteTypes = types; if (mAttachedToWindow) { updateRouteInfo(); mRouter.addCallback(types, mRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); } } private void updateRouteInfo() { updateRemoteIndicator(); updateRouteCount(); } public int getRouteTypes() { return mRouteTypes; } void updateRemoteIndicator() { final RouteInfo selected = mRouter.getSelectedRoute(mRouteTypes); final boolean isRemote = selected != mRouter.getDefaultRoute(); final boolean isConnecting = selected != null && selected.isConnecting(); boolean needsRefresh = false; if (mRemoteActive != isRemote) { mRemoteActive = isRemote; needsRefresh = true; } if (mIsConnecting != isConnecting) { mIsConnecting = isConnecting; needsRefresh = true; } if (needsRefresh) { refreshDrawableState(); } } void updateRouteCount() { final int N = mRouter.getRouteCount(); int count = 0; boolean scanRequired = false; for (int i = 0; i < N; i++) { final RouteInfo route = mRouter.getRouteAt(i); final int routeTypes = route.getSupportedTypes(); if ((routeTypes & mRouteTypes) != 0) { if (route instanceof RouteGroup) { count += ((RouteGroup) route).getRouteCount(); } else { count++; } if (((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) != 0) { scanRequired = true; } } } setEnabled(count != 0); // Only allow toggling if we have more than just user routes. // Don't toggle if we support video or remote display routes, we may have to // let the dialog scan. mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0 && !scanRequired; } @Override protected int[] onCreateDrawableState(int extraSpace) { final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); Loading Loading @@ -291,6 +258,21 @@ public class MediaRouteButton extends View { } } private void setRemoteIndicatorDrawable(Drawable d) { if (mRemoteIndicator != null) { mRemoteIndicator.setCallback(null); unscheduleDrawable(mRemoteIndicator); } mRemoteIndicator = d; if (d != null) { d.setCallback(this); d.setState(getDrawableState()); d.setVisible(getVisibility() == VISIBLE, false); } refreshDrawableState(); } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || who == mRemoteIndicator; Loading @@ -299,12 +281,16 @@ public class MediaRouteButton extends View { @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (mRemoteIndicator != null) mRemoteIndicator.jumpToCurrentState(); if (mRemoteIndicator != null) { mRemoteIndicator.jumpToCurrentState(); } } @Override public void setVisibility(int visibility) { super.setVisibility(visibility); if (mRemoteIndicator != null) { mRemoteIndicator.setVisible(getVisibility() == VISIBLE, false); } Loading @@ -313,20 +299,22 @@ public class MediaRouteButton extends View { @Override public void onAttachedToWindow() { super.onAttachedToWindow(); mAttachedToWindow = true; if (mRouteTypes != 0) { mRouter.addCallback(mRouteTypes, mRouterCallback, mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); updateRouteInfo(); } refreshRoute(); } @Override public void onDetachedFromWindow() { mAttachedToWindow = false; if (mRouteTypes != 0) { mRouter.removeCallback(mRouterCallback); mRouter.removeCallback(mCallback); } mAttachedToWindow = false; super.onDetachedFromWindow(); } Loading Loading @@ -389,93 +377,71 @@ public class MediaRouteButton extends View { final int drawLeft = left + (right - left - drawWidth) / 2; final int drawTop = top + (bottom - top - drawHeight) / 2; mRemoteIndicator.setBounds(drawLeft, drawTop, drawLeft + drawWidth, drawTop + drawHeight); mRemoteIndicator.setBounds(drawLeft, drawTop, drawLeft + drawWidth, drawTop + drawHeight); mRemoteIndicator.draw(canvas); } public void setExtendedSettingsClickListener(OnClickListener listener) { mExtendedSettingsClickListener = listener; if (mDialogFragment != null) { mDialogFragment.setExtendedSettingsClickListener(listener); } } private void refreshRoute() { if (mAttachedToWindow) { final MediaRouter.RouteInfo route = mRouter.getSelectedRoute(); final boolean isRemote = !route.isDefault() && route.matchesTypes(mRouteTypes); final boolean isConnecting = isRemote && route.isConnecting(); /** * Asynchronously show the route chooser dialog. * This will attach a {@link DialogFragment} to the containing Activity. */ public void showDialog() { final FragmentManager fm = getActivity().getFragmentManager(); if (mDialogFragment == null) { // See if one is already attached to this activity. mDialogFragment = (MediaRouteChooserDialogFragment) fm.findFragmentByTag( MediaRouteChooserDialogFragment.FRAGMENT_TAG); boolean needsRefresh = false; if (mRemoteActive != isRemote) { mRemoteActive = isRemote; needsRefresh = true; } if (mDialogFragment != null) { Log.w(TAG, "showDialog(): Already showing!"); return; if (mIsConnecting != isConnecting) { mIsConnecting = isConnecting; needsRefresh = true; } mDialogFragment = new MediaRouteChooserDialogFragment(); mDialogFragment.setExtendedSettingsClickListener(mExtendedSettingsClickListener); mDialogFragment.setLauncherListener(new MediaRouteChooserDialogFragment.LauncherListener() { @Override public void onDetached(MediaRouteChooserDialogFragment detachedFragment) { mDialogFragment = null; } }); mDialogFragment.setRouteTypes(mRouteTypes); mDialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG); if (needsRefresh) { refreshDrawableState(); } private Activity getActivity() { // Gross way of unwrapping the Activity so we can get the FragmentManager Context context = getContext(); while (context instanceof ContextWrapper && !(context instanceof Activity)) { context = ((ContextWrapper) context).getBaseContext(); } if (!(context instanceof Activity)) { throw new IllegalStateException("The MediaRouteButton's Context is not an Activity."); setEnabled(mRouter.isRouteAvailable(mRouteTypes, MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE)); } return (Activity) context; } private class MediaRouteCallback extends MediaRouter.SimpleCallback { private final class MediaRouterCallback extends MediaRouter.SimpleCallback { @Override public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { updateRemoteIndicator(); public void onRouteAdded(MediaRouter router, RouteInfo info) { refreshRoute(); } @Override public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { updateRemoteIndicator(); public void onRouteRemoved(MediaRouter router, RouteInfo info) { refreshRoute(); } @Override public void onRouteChanged(MediaRouter router, RouteInfo info) { updateRemoteIndicator(); refreshRoute(); } @Override public void onRouteAdded(MediaRouter router, RouteInfo info) { updateRouteCount(); public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { refreshRoute(); } @Override public void onRouteRemoved(MediaRouter router, RouteInfo info) { updateRouteCount(); public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { refreshRoute(); } @Override public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index) { updateRouteCount(); refreshRoute(); } @Override public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { updateRouteCount(); refreshRoute(); } } } Loading
core/java/android/app/MediaRouteActionProvider.java +92 −78 Original line number Diff line number Diff line Loading @@ -16,10 +16,7 @@ package android.app; import com.android.internal.app.MediaRouteChooserDialogFragment; import android.content.Context; import android.content.ContextWrapper; import android.media.MediaRouter; import android.media.MediaRouter.RouteInfo; import android.util.Log; Loading @@ -30,22 +27,38 @@ import android.view.ViewGroup; import java.lang.ref.WeakReference; /** * The media route action provider displays a {@link MediaRouteButton media route button} * in the application's {@link ActionBar} to allow the user to select routes and * to control the currently selected route. * <p> * The application must specify the kinds of routes that the user should be allowed * to select by specifying the route types with the {@link #setRouteTypes} method. * </p><p> * Refer to {@link MediaRouteButton} for a description of the button that will * appear in the action bar menu. Note that instead of disabling the button * when no routes are available, the action provider will instead make the * menu item invisible. In this way, the button will only be visible when it * is possible for the user to discover and select a matching route. * </p> */ public class MediaRouteActionProvider extends ActionProvider { private static final String TAG = "MediaRouteActionProvider"; private Context mContext; private MediaRouter mRouter; private MenuItem mMenuItem; private MediaRouteButton mView; private final Context mContext; private final MediaRouter mRouter; private final MediaRouterCallback mCallback; private int mRouteTypes; private MediaRouteButton mButton; private View.OnClickListener mExtendedSettingsListener; private RouterCallback mCallback; public MediaRouteActionProvider(Context context) { super(context); mContext = context; mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); mCallback = new RouterCallback(this); mCallback = new MediaRouterCallback(this); // Start with live audio by default. // TODO Update this when new route types are added; segment by API level Loading @@ -53,80 +66,74 @@ public class MediaRouteActionProvider extends ActionProvider { setRouteTypes(MediaRouter.ROUTE_TYPE_LIVE_AUDIO); } /** * Sets the types of routes that will be shown in the media route chooser dialog * launched by this button. * * @param types The route types to match. */ public void setRouteTypes(int types) { if (mRouteTypes == types) return; if (mRouteTypes != types) { // FIXME: We currently have no way of knowing whether the action provider // is still needed by the UI. Unfortunately this means the action provider // may leak callbacks until garbage collection occurs. This may result in // media route providers doing more work than necessary in the short term // while trying to discover routes that are no longer of interest to the // application. To solve this problem, the action provider will need some // indication from the framework that it is being destroyed. if (mRouteTypes != 0) { mRouter.removeCallback(mCallback); } mRouteTypes = types; if (types != 0) { mRouter.addCallback(types, mCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); mRouter.addCallback(types, mCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); } refreshRoute(); if (mButton != null) { mButton.setRouteTypes(mRouteTypes); } if (mView != null) { mView.setRouteTypes(mRouteTypes); } } public void setExtendedSettingsClickListener(View.OnClickListener listener) { mExtendedSettingsListener = listener; if (mButton != null) { mButton.setExtendedSettingsClickListener(listener); } } @Override @SuppressWarnings("deprecation") public View onCreateActionView() { throw new UnsupportedOperationException("Use onCreateActionView(MenuItem) instead."); } @Override public View onCreateActionView(MenuItem item) { if (mMenuItem != null || mView != null) { if (mButton != null) { Log.e(TAG, "onCreateActionView: this ActionProvider is already associated " + "with a menu item. Don't reuse MediaRouteActionProvider instances! " + "Abandoning the old one..."); } mMenuItem = item; mView = new MediaRouteButton(mContext); mView.setCheatSheetEnabled(true); mView.setRouteTypes(mRouteTypes); mView.setExtendedSettingsClickListener(mExtendedSettingsListener); mView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mButton = new MediaRouteButton(mContext); mButton.setCheatSheetEnabled(true); mButton.setRouteTypes(mRouteTypes); mButton.setExtendedSettingsClickListener(mExtendedSettingsListener); mButton.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); return mView; return mButton; } @Override public boolean onPerformDefaultAction() { final FragmentManager fm = getActivity().getFragmentManager(); // See if one is already attached to this activity. MediaRouteChooserDialogFragment dialogFragment = (MediaRouteChooserDialogFragment) fm.findFragmentByTag( MediaRouteChooserDialogFragment.FRAGMENT_TAG); if (dialogFragment != null) { Log.w(TAG, "onPerformDefaultAction(): Chooser dialog already showing!"); return false; } dialogFragment = new MediaRouteChooserDialogFragment(); dialogFragment.setExtendedSettingsClickListener(mExtendedSettingsListener); dialogFragment.setRouteTypes(mRouteTypes); dialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG); return true; } private Activity getActivity() { // Gross way of unwrapping the Activity so we can get the FragmentManager Context context = mContext; while (context instanceof ContextWrapper && !(context instanceof Activity)) { context = ((ContextWrapper) context).getBaseContext(); } if (!(context instanceof Activity)) { throw new IllegalStateException("The MediaRouteActionProvider's Context " + "is not an Activity."); } return (Activity) context; } public void setExtendedSettingsClickListener(View.OnClickListener listener) { mExtendedSettingsListener = listener; if (mView != null) { mView.setExtendedSettingsClickListener(listener); if (mButton != null) { return mButton.showDialogInternal(); } return false; } @Override Loading @@ -136,36 +143,43 @@ public class MediaRouteActionProvider extends ActionProvider { @Override public boolean isVisible() { return mRouter.getRouteCount() > 1; return mRouter.isRouteAvailable(mRouteTypes, MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE); } private static class RouterCallback extends MediaRouter.SimpleCallback { private WeakReference<MediaRouteActionProvider> mAp; private void refreshRoute() { refreshVisibility(); } private static class MediaRouterCallback extends MediaRouter.SimpleCallback { private final WeakReference<MediaRouteActionProvider> mProviderWeak; RouterCallback(MediaRouteActionProvider ap) { mAp = new WeakReference<MediaRouteActionProvider>(ap); public MediaRouterCallback(MediaRouteActionProvider provider) { mProviderWeak = new WeakReference<MediaRouteActionProvider>(provider); } @Override public void onRouteAdded(MediaRouter router, RouteInfo info) { final MediaRouteActionProvider ap = mAp.get(); if (ap == null) { router.removeCallback(this); return; refreshRoute(router); } ap.refreshVisibility(); @Override public void onRouteRemoved(MediaRouter router, RouteInfo info) { refreshRoute(router); } @Override public void onRouteRemoved(MediaRouter router, RouteInfo info) { final MediaRouteActionProvider ap = mAp.get(); if (ap == null) { router.removeCallback(this); return; public void onRouteChanged(MediaRouter router, RouteInfo info) { refreshRoute(router); } ap.refreshVisibility(); private void refreshRoute(MediaRouter router) { MediaRouteActionProvider provider = mProviderWeak.get(); if (provider != null) { provider.refreshRoute(); } else { router.removeCallback(this); } } } }
core/java/android/app/MediaRouteButton.java +144 −178 Original line number Diff line number Diff line Loading @@ -17,7 +17,7 @@ package android.app; import com.android.internal.R; import com.android.internal.app.MediaRouteChooserDialogFragment; import com.android.internal.app.MediaRouteDialogPresenter; import android.content.Context; import android.content.ContextWrapper; Loading @@ -30,7 +30,6 @@ import android.media.MediaRouter.RouteGroup; import android.media.MediaRouter.RouteInfo; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.SoundEffectConstants; Loading @@ -38,17 +37,15 @@ import android.view.View; import android.widget.Toast; public class MediaRouteButton extends View { private static final String TAG = "MediaRouteButton"; private final MediaRouter mRouter; private final MediaRouterCallback mCallback; private MediaRouter mRouter; private final MediaRouteCallback mRouterCallback = new MediaRouteCallback(); private int mRouteTypes; private boolean mAttachedToWindow; private Drawable mRemoteIndicator; private boolean mRemoteActive; private boolean mToggleMode; private boolean mCheatSheetEnabled; private boolean mIsConnecting; Loading @@ -56,12 +53,13 @@ public class MediaRouteButton extends View { private int mMinHeight; private OnClickListener mExtendedSettingsClickListener; private MediaRouteChooserDialogFragment mDialogFragment; // The checked state is used when connected to a remote route. private static final int[] CHECKED_STATE_SET = { R.attr.state_checked }; // The activated state is used while connecting to a remote route. private static final int[] ACTIVATED_STATE_SET = { R.attr.state_activated }; Loading @@ -78,6 +76,7 @@ public class MediaRouteButton extends View { super(context, attrs, defStyleAttr); mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE); mCallback = new MediaRouterCallback(); TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, 0); Loading @@ -98,53 +97,99 @@ public class MediaRouteButton extends View { setRouteTypes(routeTypes); } private void setRemoteIndicatorDrawable(Drawable d) { if (mRemoteIndicator != null) { mRemoteIndicator.setCallback(null); unscheduleDrawable(mRemoteIndicator); /** * Gets the media route types for filtering the routes that the user can * select using the media route chooser dialog. * * @return The route types. */ public int getRouteTypes() { return mRouteTypes; } mRemoteIndicator = d; if (d != null) { d.setCallback(this); d.setState(getDrawableState()); d.setVisible(getVisibility() == VISIBLE, false); /** * Sets the types of routes that will be shown in the media route chooser dialog * launched by this button. * * @param types The route types to match. */ public void setRouteTypes(int types) { if (mRouteTypes != types) { if (mAttachedToWindow && mRouteTypes != 0) { mRouter.removeCallback(mCallback); } refreshDrawableState(); mRouteTypes = types; if (mAttachedToWindow && types != 0) { mRouter.addCallback(types, mCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); } @Override public boolean performClick() { // Send the appropriate accessibility events and call listeners boolean handled = super.performClick(); if (!handled) { playSoundEffect(SoundEffectConstants.CLICK); refreshRoute(); } } if (mToggleMode) { if (mRemoteActive) { mRouter.selectRouteInt(mRouteTypes, mRouter.getDefaultRoute(), true); } else { final int N = mRouter.getRouteCount(); for (int i = 0; i < N; i++) { final RouteInfo route = mRouter.getRouteAt(i); if ((route.getSupportedTypes() & mRouteTypes) != 0 && route != mRouter.getDefaultRoute()) { mRouter.selectRouteInt(mRouteTypes, route, true); public void setExtendedSettingsClickListener(OnClickListener listener) { mExtendedSettingsClickListener = listener; } /** * Show the route chooser or controller dialog. * <p> * If the default route is selected or if the currently selected route does * not match the {@link #getRouteTypes route types}, then shows the route chooser dialog. * Otherwise, shows the route controller dialog to offer the user * a choice to disconnect from the route or perform other control actions * such as setting the route's volume. * </p><p> * This will attach a {@link DialogFragment} to the containing Activity. * </p> */ public void showDialog() { showDialogInternal(); } boolean showDialogInternal() { if (!mAttachedToWindow) { return false; } } else { showDialog(); DialogFragment f = MediaRouteDialogPresenter.showDialogFragment(getActivity(), mRouteTypes, mExtendedSettingsClickListener); return f != null; } return handled; private Activity getActivity() { // Gross way of unwrapping the Activity so we can get the FragmentManager Context context = getContext(); while (context instanceof ContextWrapper) { if (context instanceof Activity) { return (Activity)context; } context = ((ContextWrapper)context).getBaseContext(); } throw new IllegalStateException("The MediaRouteButton's Context is not an Activity."); } /** * Sets whether to enable showing a toast with the content descriptor of the * button when the button is long pressed. */ void setCheatSheetEnabled(boolean enable) { mCheatSheetEnabled = enable; } @Override public boolean performClick() { // Send the appropriate accessibility events and call listeners boolean handled = super.performClick(); if (!handled) { playSoundEffect(SoundEffectConstants.CLICK); } return showDialogInternal() || handled; } @Override public boolean performLongClick() { if (super.performLongClick()) { Loading Loading @@ -183,87 +228,9 @@ public class MediaRouteButton extends View { } cheatSheet.show(); performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); return true; } public void setRouteTypes(int types) { if (types == mRouteTypes) { // Already registered; nothing to do. return; } if (mAttachedToWindow && mRouteTypes != 0) { mRouter.removeCallback(mRouterCallback); } mRouteTypes = types; if (mAttachedToWindow) { updateRouteInfo(); mRouter.addCallback(types, mRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); } } private void updateRouteInfo() { updateRemoteIndicator(); updateRouteCount(); } public int getRouteTypes() { return mRouteTypes; } void updateRemoteIndicator() { final RouteInfo selected = mRouter.getSelectedRoute(mRouteTypes); final boolean isRemote = selected != mRouter.getDefaultRoute(); final boolean isConnecting = selected != null && selected.isConnecting(); boolean needsRefresh = false; if (mRemoteActive != isRemote) { mRemoteActive = isRemote; needsRefresh = true; } if (mIsConnecting != isConnecting) { mIsConnecting = isConnecting; needsRefresh = true; } if (needsRefresh) { refreshDrawableState(); } } void updateRouteCount() { final int N = mRouter.getRouteCount(); int count = 0; boolean scanRequired = false; for (int i = 0; i < N; i++) { final RouteInfo route = mRouter.getRouteAt(i); final int routeTypes = route.getSupportedTypes(); if ((routeTypes & mRouteTypes) != 0) { if (route instanceof RouteGroup) { count += ((RouteGroup) route).getRouteCount(); } else { count++; } if (((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) != 0) { scanRequired = true; } } } setEnabled(count != 0); // Only allow toggling if we have more than just user routes. // Don't toggle if we support video or remote display routes, we may have to // let the dialog scan. mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0 && !scanRequired; } @Override protected int[] onCreateDrawableState(int extraSpace) { final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); Loading Loading @@ -291,6 +258,21 @@ public class MediaRouteButton extends View { } } private void setRemoteIndicatorDrawable(Drawable d) { if (mRemoteIndicator != null) { mRemoteIndicator.setCallback(null); unscheduleDrawable(mRemoteIndicator); } mRemoteIndicator = d; if (d != null) { d.setCallback(this); d.setState(getDrawableState()); d.setVisible(getVisibility() == VISIBLE, false); } refreshDrawableState(); } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || who == mRemoteIndicator; Loading @@ -299,12 +281,16 @@ public class MediaRouteButton extends View { @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (mRemoteIndicator != null) mRemoteIndicator.jumpToCurrentState(); if (mRemoteIndicator != null) { mRemoteIndicator.jumpToCurrentState(); } } @Override public void setVisibility(int visibility) { super.setVisibility(visibility); if (mRemoteIndicator != null) { mRemoteIndicator.setVisible(getVisibility() == VISIBLE, false); } Loading @@ -313,20 +299,22 @@ public class MediaRouteButton extends View { @Override public void onAttachedToWindow() { super.onAttachedToWindow(); mAttachedToWindow = true; if (mRouteTypes != 0) { mRouter.addCallback(mRouteTypes, mRouterCallback, mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); updateRouteInfo(); } refreshRoute(); } @Override public void onDetachedFromWindow() { mAttachedToWindow = false; if (mRouteTypes != 0) { mRouter.removeCallback(mRouterCallback); mRouter.removeCallback(mCallback); } mAttachedToWindow = false; super.onDetachedFromWindow(); } Loading Loading @@ -389,93 +377,71 @@ public class MediaRouteButton extends View { final int drawLeft = left + (right - left - drawWidth) / 2; final int drawTop = top + (bottom - top - drawHeight) / 2; mRemoteIndicator.setBounds(drawLeft, drawTop, drawLeft + drawWidth, drawTop + drawHeight); mRemoteIndicator.setBounds(drawLeft, drawTop, drawLeft + drawWidth, drawTop + drawHeight); mRemoteIndicator.draw(canvas); } public void setExtendedSettingsClickListener(OnClickListener listener) { mExtendedSettingsClickListener = listener; if (mDialogFragment != null) { mDialogFragment.setExtendedSettingsClickListener(listener); } } private void refreshRoute() { if (mAttachedToWindow) { final MediaRouter.RouteInfo route = mRouter.getSelectedRoute(); final boolean isRemote = !route.isDefault() && route.matchesTypes(mRouteTypes); final boolean isConnecting = isRemote && route.isConnecting(); /** * Asynchronously show the route chooser dialog. * This will attach a {@link DialogFragment} to the containing Activity. */ public void showDialog() { final FragmentManager fm = getActivity().getFragmentManager(); if (mDialogFragment == null) { // See if one is already attached to this activity. mDialogFragment = (MediaRouteChooserDialogFragment) fm.findFragmentByTag( MediaRouteChooserDialogFragment.FRAGMENT_TAG); boolean needsRefresh = false; if (mRemoteActive != isRemote) { mRemoteActive = isRemote; needsRefresh = true; } if (mDialogFragment != null) { Log.w(TAG, "showDialog(): Already showing!"); return; if (mIsConnecting != isConnecting) { mIsConnecting = isConnecting; needsRefresh = true; } mDialogFragment = new MediaRouteChooserDialogFragment(); mDialogFragment.setExtendedSettingsClickListener(mExtendedSettingsClickListener); mDialogFragment.setLauncherListener(new MediaRouteChooserDialogFragment.LauncherListener() { @Override public void onDetached(MediaRouteChooserDialogFragment detachedFragment) { mDialogFragment = null; } }); mDialogFragment.setRouteTypes(mRouteTypes); mDialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG); if (needsRefresh) { refreshDrawableState(); } private Activity getActivity() { // Gross way of unwrapping the Activity so we can get the FragmentManager Context context = getContext(); while (context instanceof ContextWrapper && !(context instanceof Activity)) { context = ((ContextWrapper) context).getBaseContext(); } if (!(context instanceof Activity)) { throw new IllegalStateException("The MediaRouteButton's Context is not an Activity."); setEnabled(mRouter.isRouteAvailable(mRouteTypes, MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE)); } return (Activity) context; } private class MediaRouteCallback extends MediaRouter.SimpleCallback { private final class MediaRouterCallback extends MediaRouter.SimpleCallback { @Override public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { updateRemoteIndicator(); public void onRouteAdded(MediaRouter router, RouteInfo info) { refreshRoute(); } @Override public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { updateRemoteIndicator(); public void onRouteRemoved(MediaRouter router, RouteInfo info) { refreshRoute(); } @Override public void onRouteChanged(MediaRouter router, RouteInfo info) { updateRemoteIndicator(); refreshRoute(); } @Override public void onRouteAdded(MediaRouter router, RouteInfo info) { updateRouteCount(); public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { refreshRoute(); } @Override public void onRouteRemoved(MediaRouter router, RouteInfo info) { updateRouteCount(); public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { refreshRoute(); } @Override public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index) { updateRouteCount(); refreshRoute(); } @Override public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { updateRouteCount(); refreshRoute(); } } }