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

Commit 5773f724 authored by Jeff Brown's avatar Jeff Brown Committed by Android Git Automerger
Browse files

am dd4f9e8b: Merge "Update the media router dialogs and integrate into system UI." into klp-dev

* commit 'dd4f9e8b':
  Update the media router dialogs and integrate into system UI.
parents 3105b6d2 dd4f9e8b
Loading
Loading
Loading
Loading
+92 −78
Original line number Diff line number Diff line
@@ -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;
@@ -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
@@ -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
@@ -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);
            }
        }
    }
}
+144 −178
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;

@@ -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
    };
@@ -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);
@@ -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()) {
@@ -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);
@@ -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;
@@ -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);
        }
@@ -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();
    }

@@ -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();
        }
    }
}
+272 −0

File added.

Preview size limit exceeded, changes collapsed.

+49 −638

File changed.

Preview size limit exceeded, changes collapsed.

+318 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading