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

Commit 690ffb4e authored by Adam Powell's avatar Adam Powell
Browse files

More fun with MediaRouter

Add action provider, button, and styles. Extend ActionProvider to
allow for getting references to MenuItem instances.

Implement toggle mode for the MediaRouteButton/ActionProvider. Dialog
selection yet to come.

Change-Id: Ibe3188570f503bbf8dd00cf154663435656a7171
parent 3676b137
Loading
Loading
Loading
Loading
+25 −2
Original line number Diff line number Diff line
@@ -675,6 +675,7 @@ package android {
    field public static final int maxWidth = 16843039; // 0x101011f
    field public static final int measureAllChildren = 16843018; // 0x101010a
    field public static final int measureWithLargestChild = 16843476; // 0x10102d4
    field public static final int mediaRouteButtonStyle = 16843693; // 0x10103ad
    field public static final int menuCategory = 16843230; // 0x10101de
    field public static final int mimeType = 16842790; // 0x1010026
    field public static final int minDate = 16843583; // 0x101033f
@@ -1789,6 +1790,7 @@ package android {
    field public static final int Widget_DeviceDefault_Light_ListPopupWindow = 16974235; // 0x103019b
    field public static final int Widget_DeviceDefault_Light_ListView = 16974210; // 0x1030182
    field public static final int Widget_DeviceDefault_Light_ListView_DropDown = 16974205; // 0x103017d
    field public static final int Widget_DeviceDefault_Light_MediaRouteButton = 16974296; // 0x10301d8
    field public static final int Widget_DeviceDefault_Light_PopupMenu = 16974236; // 0x103019c
    field public static final int Widget_DeviceDefault_Light_PopupWindow = 16974211; // 0x1030183
    field public static final int Widget_DeviceDefault_Light_ProgressBar = 16974212; // 0x1030184
@@ -1814,6 +1816,7 @@ package android {
    field public static final int Widget_DeviceDefault_ListPopupWindow = 16974180; // 0x1030164
    field public static final int Widget_DeviceDefault_ListView = 16974158; // 0x103014e
    field public static final int Widget_DeviceDefault_ListView_DropDown = 16974153; // 0x1030149
    field public static final int Widget_DeviceDefault_MediaRouteButton = 16974295; // 0x10301d7
    field public static final int Widget_DeviceDefault_PopupMenu = 16974181; // 0x1030165
    field public static final int Widget_DeviceDefault_PopupWindow = 16974159; // 0x103014f
    field public static final int Widget_DeviceDefault_ProgressBar = 16974160; // 0x1030150
@@ -1905,6 +1908,7 @@ package android {
    field public static final int Widget_Holo_Light_ListPopupWindow = 16974043; // 0x10300db
    field public static final int Widget_Holo_Light_ListView = 16974018; // 0x10300c2
    field public static final int Widget_Holo_Light_ListView_DropDown = 16974013; // 0x10300bd
    field public static final int Widget_Holo_Light_MediaRouteButton = 16974294; // 0x10301d6
    field public static final int Widget_Holo_Light_PopupMenu = 16974044; // 0x10300dc
    field public static final int Widget_Holo_Light_PopupWindow = 16974019; // 0x10300c3
    field public static final int Widget_Holo_Light_ProgressBar = 16974020; // 0x10300c4
@@ -1930,6 +1934,7 @@ package android {
    field public static final int Widget_Holo_ListPopupWindow = 16973997; // 0x10300ad
    field public static final int Widget_Holo_ListView = 16973975; // 0x1030097
    field public static final int Widget_Holo_ListView_DropDown = 16973970; // 0x1030092
    field public static final int Widget_Holo_MediaRouteButton = 16974293; // 0x10301d5
    field public static final int Widget_Holo_PopupMenu = 16973998; // 0x10300ae
    field public static final int Widget_Holo_PopupWindow = 16973976; // 0x1030098
    field public static final int Widget_Holo_ProgressBar = 16973977; // 0x1030099
@@ -3671,6 +3676,20 @@ package android.app {
    method public android.view.Window startActivity(java.lang.String, android.content.Intent);
  }
  public class MediaRouteActionProvider extends android.view.ActionProvider {
    ctor public MediaRouteActionProvider(android.content.Context);
    method public android.view.View onCreateActionView();
    method public void setRouteTypes(int);
  }
  public class MediaRouteButton extends android.view.View {
    ctor public MediaRouteButton(android.content.Context);
    ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet);
    ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet, int);
    method public int getRouteTypes();
    method public void setRouteTypes(int);
  }
  public class NativeActivity extends android.app.Activity implements android.view.InputQueue.Callback android.view.SurfaceHolder.Callback2 android.view.ViewTreeObserver.OnGlobalLayoutListener {
    ctor public NativeActivity();
    method public void onGlobalLayout();
@@ -11489,6 +11508,7 @@ package android.media {
  public class MediaRouter {
    method public void addCallback(int, android.media.MediaRouter.Callback);
    method public void addUserRoute(android.media.MediaRouter.UserRouteInfo);
    method public void clearUserRoutes();
    method public android.media.MediaRouter.RouteCategory createRouteCategory(java.lang.CharSequence, boolean);
    method public android.media.MediaRouter.UserRouteInfo createUserRoute(android.media.MediaRouter.RouteCategory);
    method public static android.media.MediaRouter forApplication(android.content.Context);
@@ -11496,10 +11516,11 @@ package android.media {
    method public int getCategoryCount();
    method public android.media.MediaRouter.RouteInfo getRouteAt(int);
    method public int getRouteCount();
    method public android.media.MediaRouter.RouteInfo getSelectedRoute(int);
    method public void removeCallback(android.media.MediaRouter.Callback);
    method public void removeUserRoute(android.media.MediaRouter.UserRouteInfo);
    method public void selectRoute(int, android.media.MediaRouter.RouteInfo);
    method public void setRouteVolume(int, float);
    method public void setSelectedRouteVolume(int, float);
    field public static final int ROUTE_TYPE_LIVE_AUDIO = 1; // 0x1
    field public static final int ROUTE_TYPE_USER = 8388608; // 0x800000
  }
@@ -11534,6 +11555,7 @@ package android.media {
    method public java.lang.CharSequence getName();
    method public java.lang.CharSequence getStatus();
    method public int getSupportedTypes();
    method public float getVolume();
  }
  public static class MediaRouter.SimpleCallback implements android.media.MediaRouter.Callback {
@@ -22775,7 +22797,8 @@ package android.view {
  public abstract class ActionProvider {
    ctor public ActionProvider(android.content.Context);
    method public boolean hasSubMenu();
    method public abstract android.view.View onCreateActionView();
    method public abstract deprecated android.view.View onCreateActionView();
    method public android.view.View onCreateActionView(android.view.MenuItem);
    method public boolean onPerformDefaultAction();
    method public void onPrepareSubMenu(android.view.SubMenu);
  }
+99 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app;

import android.content.Context;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteInfo;
import android.util.Log;
import android.view.ActionProvider;
import android.view.MenuItem;
import android.view.View;

public class MediaRouteActionProvider extends ActionProvider {
    private static final String TAG = "MediaRouteActionProvider";

    private Context mContext;
    private MediaRouter mRouter;
    private MenuItem mMenuItem;
    private MediaRouteButton mView;
    private int mRouteTypes;
    private final RouterCallback mRouterCallback = new RouterCallback();

    public MediaRouteActionProvider(Context context) {
        super(context);
        mContext = context;
        mRouter = MediaRouter.forApplication(context);

        // Start with live audio by default.
        // TODO Update this when new route types are added; segment by API level
        // when different route types were added.
        setRouteTypes(MediaRouter.ROUTE_TYPE_LIVE_AUDIO);
    }

    public void setRouteTypes(int types) {
        if (types == mRouteTypes) {
            // Already registered; nothing to do.
            return;
        }
        if (mRouteTypes != 0) {
            mRouter.removeCallback(mRouterCallback);
        }
        mRouteTypes = types;
        if (mView != null) {
            mView.setRouteTypes(mRouteTypes);
        }
        mRouter.addCallback(types, mRouterCallback);
    }

    @Override
    public View onCreateActionView() {
        throw new UnsupportedOperationException("Use onCreateActionView(MenuItem) instead.");
    }

    @Override
    public View onCreateActionView(MenuItem item) {
        if (mMenuItem != null || mView != 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);
        mMenuItem.setVisible(mRouter.getRouteCount() > 1);
        mView.setRouteTypes(mRouteTypes);
        return mView;
    }

    @Override
    public boolean onPerformDefaultAction() {
        // Show routing dialog
        return true;
    }

    private class RouterCallback extends MediaRouter.SimpleCallback {
        @Override
        public void onRouteAdded(int type, RouteInfo info) {
            mMenuItem.setVisible(mRouter.getRouteCount() > 1);
        }

        @Override
        public void onRouteRemoved(int type, RouteInfo info) {
            mMenuItem.setVisible(mRouter.getRouteCount() > 1);
        }
    }
}
+284 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app;

import com.android.internal.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteInfo;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SoundEffectConstants;
import android.view.View;

public class MediaRouteButton extends View {
    private static final String TAG = "MediaRouteButton";

    private MediaRouter mRouter;
    private final MediaRouteCallback mRouterCallback = new MediaRouteCallback();
    private int mRouteTypes;

    private Drawable mRemoteIndicator;
    private boolean mRemoteActive;
    private boolean mToggleMode;

    private int mMinWidth;
    private int mMinHeight;

    private static final int[] ACTIVATED_STATE_SET = {
        R.attr.state_activated
    };

    public MediaRouteButton(Context context) {
        this(context, null);
    }

    public MediaRouteButton(Context context, AttributeSet attrs) {
        this(context, null, com.android.internal.R.attr.mediaRouteButtonStyle);
    }

    public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mRouter = MediaRouter.forApplication(context);

        TypedArray a = context.obtainStyledAttributes(attrs,
                com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, 0);
        setRemoteIndicatorDrawable(a.getDrawable(
                com.android.internal.R.styleable.MediaRouteButton_externalRouteEnabledDrawable));
        mMinWidth = a.getDimensionPixelSize(
                com.android.internal.R.styleable.MediaRouteButton_minWidth, 0);
        mMinHeight = a.getDimensionPixelSize(
                com.android.internal.R.styleable.MediaRouteButton_minHeight, 0);
        a.recycle();

        setClickable(true);
    }

    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
    public boolean performClick() {
        // Send the appropriate accessibility events and call listeners
        boolean handled = super.performClick();
        if (!handled) {
            playSoundEffect(SoundEffectConstants.CLICK);
        }

        if (mToggleMode) {
            if (mRemoteActive) {
                mRouter.selectRoute(mRouteTypes, mRouter.getSystemAudioRoute());
            } 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.getSystemAudioRoute()) {
                        mRouter.selectRoute(mRouteTypes, route);
                    }
                }
            }
        } else {
            Log.d(TAG, "TODO: Implement the dialog!");
        }

        return handled;
    }

    public void setRouteTypes(int types) {
        if (types == mRouteTypes) {
            // Already registered; nothing to do.
            return;
        }
        if (mRouteTypes != 0) {
            mRouter.removeCallback(mRouterCallback);
        }
        mRouteTypes = types;
        updateRemoteIndicator();
        updateRouteCount();
        mRouter.addCallback(types, mRouterCallback);
    }

    public int getRouteTypes() {
        return mRouteTypes;
    }

    void updateRemoteIndicator() {
        final boolean isRemote =
                mRouter.getSelectedRoute(mRouteTypes) != mRouter.getSystemAudioRoute();
        if (mRemoteActive != isRemote) {
            mRemoteActive = isRemote;
            refreshDrawableState();
        }
    }

    void updateRouteCount() {
        final int N = mRouter.getRouteCount();
        int count = 0;
        for (int i = 0; i < N; i++) {
            if ((mRouter.getRouteAt(i).getSupportedTypes() & mRouteTypes) != 0) {
                count++;
            }
        }

        setEnabled(count != 0);

        // Only allow toggling if we have more than just user routes
        mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0;
    }

    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        if (mRemoteActive) {
            mergeDrawableStates(drawableState, ACTIVATED_STATE_SET);
        }
        return drawableState;
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();

        if (mRemoteIndicator != null) {
            int[] myDrawableState = getDrawableState();
            mRemoteIndicator.setState(myDrawableState);
            invalidate();
        }
    }

    @Override
    protected boolean verifyDrawable(Drawable who) {
        return super.verifyDrawable(who) || who == mRemoteIndicator;
    }

    @Override
    public void jumpDrawablesToCurrentState() {
        super.jumpDrawablesToCurrentState();
        if (mRemoteIndicator != null) mRemoteIndicator.jumpToCurrentState();
    }

    @Override
    public void setVisibility(int visibility) {
        super.setVisibility(visibility);
        if (mRemoteIndicator != null) {
            mRemoteIndicator.setVisible(getVisibility() == VISIBLE, false);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        final int minWidth = Math.max(mMinWidth,
                mRemoteIndicator != null ? mRemoteIndicator.getIntrinsicWidth() : 0);
        final int minHeight = Math.max(mMinHeight,
                mRemoteIndicator != null ? mRemoteIndicator.getIntrinsicHeight() : 0);

        int width;
        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                width = widthSize;
                break;
            case MeasureSpec.AT_MOST:
                width = Math.min(widthSize, minWidth + getPaddingLeft() + getPaddingRight());
                break;
            default:
            case MeasureSpec.UNSPECIFIED:
                width = minWidth + getPaddingLeft() + getPaddingRight();
                break;
        }

        int height;
        switch (heightMode) {
            case MeasureSpec.EXACTLY:
                height = heightSize;
                break;
            case MeasureSpec.AT_MOST:
                height = Math.min(heightSize, minHeight + getPaddingTop() + getPaddingBottom());
                break;
            default:
            case MeasureSpec.UNSPECIFIED:
                height = minHeight + getPaddingTop() + getPaddingBottom();
                break;
        }

        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mRemoteIndicator == null) return;

        final int left = getPaddingLeft();
        final int right = getWidth() - getPaddingRight();
        final int top = getPaddingTop();
        final int bottom = getHeight() - getPaddingBottom();

        final int drawWidth = mRemoteIndicator.getIntrinsicWidth();
        final int drawHeight = mRemoteIndicator.getIntrinsicHeight();
        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.draw(canvas);
    }

    private class MediaRouteCallback extends MediaRouter.SimpleCallback {
        @Override
        public void onRouteSelected(int type, RouteInfo info) {
            updateRemoteIndicator();
        }

        @Override
        public void onRouteUnselected(int type, RouteInfo info) {
            updateRemoteIndicator();
        }

        @Override
        public void onRouteAdded(int type, RouteInfo info) {
            updateRouteCount();
        }

        @Override
        public void onRouteRemoved(int type, RouteInfo info) {
            updateRouteCount();
        }
    }
}
+25 −2
Original line number Diff line number Diff line
@@ -58,7 +58,8 @@ public abstract class ActionProvider {
    private SubUiVisibilityListener mSubUiVisibilityListener;

    /**
     * Creates a new instance.
     * Creates a new instance. ActionProvider classes should always implement a
     * constructor that takes a single Context parameter for inflating from menu XML.
     *
     * @param context Context for accessing resources.
     */
@@ -66,12 +67,34 @@ public abstract class ActionProvider {
    }

    /**
     * Factory method for creating new action views.
     * Factory method called by the Android framework to create new action views.
     *
     * <p>This method has been deprecated in favor of {@link #onCreateActionView(MenuItem)}.
     * Newer apps that wish to support platform versions prior to API 16 should also
     * implement this method to return a valid action view.</p>
     *
     * @return A new action view.
     *
     * @deprecated use {@link #onCreateActionView(MenuItem)}
     */
    public abstract View onCreateActionView();

    /**
     * Factory method called by the Android framework to create new action views.
     * This method returns a new action view for the given MenuItem.
     *
     * <p>If your ActionProvider implementation overrides the deprecated no-argument overload
     * {@link #onCreateActionView()}, overriding this method for devices running API 16 or later
     * is recommended but optional. The default implementation calls {@link #onCreateActionView()}
     * for compatibility with applications written for older platform versions.</p>
     *
     * @param forItem MenuItem to create the action view for
     * @return the new action view
     */
    public View onCreateActionView(MenuItem forItem) {
        return onCreateActionView();
    }

    /**
     * Performs an optional default action.
     * <p>
+1 −1
Original line number Diff line number Diff line
@@ -574,7 +574,7 @@ public final class MenuItemImpl implements MenuItem {
        if (mActionView != null) {
            return mActionView;
        } else if (mActionProvider != null) {
            mActionView = mActionProvider.onCreateActionView();
            mActionView = mActionProvider.onCreateActionView(this);
            return mActionView;
        } else {
            return null;
Loading