diff options
32 files changed, 601 insertions, 9 deletions
diff --git a/api/current.txt b/api/current.txt index 9540eab67537..a416e30858de 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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); } diff --git a/core/java/android/app/MediaRouteActionProvider.java b/core/java/android/app/MediaRouteActionProvider.java new file mode 100644 index 000000000000..301b125794b2 --- /dev/null +++ b/core/java/android/app/MediaRouteActionProvider.java @@ -0,0 +1,99 @@ +/* + * 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); + } + } +} diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java new file mode 100644 index 000000000000..3aabd5d0d561 --- /dev/null +++ b/core/java/android/app/MediaRouteButton.java @@ -0,0 +1,284 @@ +/* + * 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(); + } + } +} diff --git a/core/java/android/view/ActionProvider.java b/core/java/android/view/ActionProvider.java index 9150d19e57e8..fa79d360ed26 100644 --- a/core/java/android/view/ActionProvider.java +++ b/core/java/android/view/ActionProvider.java @@ -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,13 +67,35 @@ 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> * For the case of an action provider placed in a menu item not shown as an action this diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java index 2564921c909f..de75962d5c1e 100644 --- a/core/java/com/android/internal/view/menu/MenuItemImpl.java +++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java @@ -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; diff --git a/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png Binary files differnew file mode 100644 index 000000000000..6c607753d10e --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_light.png Binary files differnew file mode 100644 index 000000000000..a608d1cb0a55 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_light.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png Binary files differnew file mode 100644 index 000000000000..a748f2d5b9e7 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png Binary files differnew file mode 100644 index 000000000000..cfaba460138f --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png Binary files differnew file mode 100644 index 000000000000..031bbcef9090 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png Binary files differnew file mode 100644 index 000000000000..0fe15affbcb8 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_dark.png Binary files differnew file mode 100644 index 000000000000..c3da1617e3c0 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_dark.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_light.png Binary files differnew file mode 100644 index 000000000000..d069a79fc3ee --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_media_route_disabled_holo_light.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png Binary files differnew file mode 100644 index 000000000000..858f1c95ebd2 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png Binary files differnew file mode 100644 index 000000000000..babb87b825e5 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png Binary files differnew file mode 100644 index 000000000000..6dd599154e9b --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png Binary files differnew file mode 100644 index 000000000000..ff7e95abc801 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png Binary files differnew file mode 100644 index 000000000000..ed4709d1cba0 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png Binary files differnew file mode 100644 index 000000000000..668b53e32175 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png Binary files differnew file mode 100644 index 000000000000..0f7291448501 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png Binary files differnew file mode 100644 index 000000000000..be7085ec1a2d --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png Binary files differnew file mode 100644 index 000000000000..1338c8cb9280 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png Binary files differnew file mode 100644 index 000000000000..91686c6b6e68 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png diff --git a/core/res/res/drawable/ic_media_route_holo_dark.xml b/core/res/res/drawable/ic_media_route_holo_dark.xml new file mode 100644 index 000000000000..0b267d7cff95 --- /dev/null +++ b/core/res/res/drawable/ic_media_route_holo_dark.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_activated="true" android:state_enabled="true" android:drawable="@android:drawable/ic_media_route_on_holo_dark" /> + <item android:state_enabled="true" android:drawable="@android:drawable/ic_media_route_off_holo_dark" /> + <item android:drawable="@android:drawable/ic_media_route_disabled_holo_dark" /> +</selector> diff --git a/core/res/res/drawable/ic_media_route_holo_light.xml b/core/res/res/drawable/ic_media_route_holo_light.xml new file mode 100644 index 000000000000..377253a2e077 --- /dev/null +++ b/core/res/res/drawable/ic_media_route_holo_light.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_activated="true" android:state_enabled="true" android:drawable="@android:drawable/ic_media_route_on_holo_light" /> + <item android:state_enabled="true" android:drawable="@android:drawable/ic_media_route_off_holo_light" /> + <item android:drawable="@android:drawable/ic_media_route_disabled_holo_light" /> +</selector> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index e9a338503313..a68011de7364 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -828,6 +828,9 @@ <!-- Default style for the Switch widget. --> <attr name="switchStyle" format="reference" /> + <!-- Default style for the MediaRouteButton widget. --> + <attr name="mediaRouteButtonStyle" format="reference" /> + <!-- ============== --> <!-- Pointer styles --> <!-- ============== --> @@ -5671,4 +5674,16 @@ <!-- The key character map file resource. --> <attr name="keyboardLayout" format="reference" /> </declare-styleable> + + <declare-styleable name="MediaRouteButton"> + <!-- This drawable is a state list where the "activated" state + indicates active media routing. Non-activated indicates + that media is playing to the local device only. + @hide --> + <attr name="externalRouteEnabledDrawable" format="reference" /> + + <attr name="minWidth" /> + <attr name="minHeight" /> + </declare-styleable> + </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 801fdf6201ee..ebff30d1f220 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1170,6 +1170,9 @@ <java-symbol type="style" name="Theme.IconMenu" /> <java-symbol type="style" name="Theme.Panel.Volume" /> + <java-symbol type="attr" name="mediaRouteButtonStyle" /> + <java-symbol type="attr" name="externalRouteEnabledDrawable" /> + <!-- From android.policy --> <java-symbol type="anim" name="app_starting_exit" /> <java-symbol type="anim" name="lock_screen_behind_enter" /> @@ -3632,4 +3635,10 @@ <public type="attr" name="keyboardLayout" id="0x010103ab" /> <public type="attr" name="fontFamily" id="0x010103ac" /> + <public type="attr" name="mediaRouteButtonStyle" id="0x010103ad" /> + <public type="style" name="Widget.Holo.MediaRouteButton" id="0x010301d5" /> + <public type="style" name="Widget.Holo.Light.MediaRouteButton" id="0x010301d6" /> + <public type="style" name="Widget.DeviceDefault.MediaRouteButton" id="0x010301d7" /> + <public type="style" name="Widget.DeviceDefault.Light.MediaRouteButton" id="0x010301d8" /> + </resources> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 223d17a4aa2e..889f86f082eb 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -2445,4 +2445,19 @@ please see styles_device_defaults.xml. <item name="android:layout_height">wrap_content</item> <item name="android:orientation">vertical</item> </style> + + <style name="Widget.Holo.MediaRouteButton"> + <item name="android:background">?android:attr/selectableItemBackground</item> + <item name="android:externalRouteEnabledDrawable">@drawable/ic_media_route_holo_dark</item> + <item name="android:minWidth">56dp</item> + <item name="android:minHeight">48dp</item> + </style> + + <style name="Widget.Holo.Light.MediaRouteButton"> + <item name="android:background">?android:attr/selectableItemBackground</item> + <item name="android:externalRouteEnabledDrawable">@drawable/ic_media_route_holo_light</item> + <item name="android:minWidth">56dp</item> + <item name="android:minHeight">48dp</item> + </style> + </resources> diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml index 330e68c0e462..28fed45ac2dd 100644 --- a/core/res/res/values/styles_device_defaults.xml +++ b/core/res/res/values/styles_device_defaults.xml @@ -720,4 +720,8 @@ easier. <style name="DeviceDefault.Light.SegmentedButton" parent="Holo.Light.SegmentedButton" > </style> + + <style name="Widget.DeviceDefault.MediaRouteButton" parent="Widget.Holo.MediaRouteButton" /> + <style name="Widget.DeviceDefault.Light.MediaRouteButton" parent="Widget.Holo.Light.MediaRouteButton" /> + </resources> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 77dbaa57206d..83d0f63672b6 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -282,6 +282,8 @@ please see themes_device_defaults.xml. <item name="popupMenuStyle">@android:style/Widget.PopupMenu</item> <item name="activityChooserViewStyle">@android:style/Widget.ActivityChooserView</item> + <item name="mediaRouteButtonStyle">@android:style/Widget.DeviceDefault.MediaRouteButton</item> + <!-- Preference styles --> <item name="preferenceScreenStyle">@android:style/Preference.PreferenceScreen</item> <item name="preferenceFragmentStyle">@style/PreferenceFragment</item> @@ -463,6 +465,8 @@ please see themes_device_defaults.xml. <item name="searchViewEditQuery">@android:drawable/ic_commit_search_api_holo_light</item> <item name="detailsElementBackground">@android:drawable/panel_bg_holo_light</item> + + <item name="mediaRouteButtonStyle">@android:style/Widget.DeviceDefault.Light.MediaRouteButton</item> </style> <!-- Variant of {@link #Theme_Light} with no title bar --> @@ -940,6 +944,7 @@ please see themes_device_defaults.xml. <item name="buttonStyleToggle">@android:style/Widget.Holo.Button.Toggle</item> <item name="switchStyle">@android:style/Widget.Holo.CompoundButton.Switch</item> + <item name="mediaRouteButtonStyle">@android:style/Widget.Holo.MediaRouteButton</item> <item name="selectableItemBackground">@android:drawable/item_background_holo_dark</item> <item name="borderlessButtonStyle">@android:style/Widget.Holo.Button.Borderless</item> @@ -1242,6 +1247,7 @@ please see themes_device_defaults.xml. <item name="buttonStyleToggle">@android:style/Widget.Holo.Light.Button.Toggle</item> <item name="switchStyle">@android:style/Widget.Holo.Light.CompoundButton.Switch</item> + <item name="mediaRouteButtonStyle">@android:style/Widget.Holo.Light.MediaRouteButton</item> <item name="selectableItemBackground">@android:drawable/item_background_holo_light</item> <item name="borderlessButtonStyle">@android:style/Widget.Holo.Light.Button.Borderless</item> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index ae9255a3a907..eef831f412da 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -190,6 +190,8 @@ easier. <!-- DatePicker style --> <item name="datePickerStyle">@style/Widget.DeviceDefault.DatePicker</item> + + <item name="mediaRouteButtonStyle">@android:style/Widget.DeviceDefault.MediaRouteButton</item> </style> <!-- Variant of {@link #Theme_DeviceDefault} with no action bar --> @@ -337,6 +339,8 @@ easier. <!-- DatePicker style --> <item name="datePickerStyle">@style/Widget.DeviceDefault.Light.DatePicker</item> + + <item name="mediaRouteButtonStyle">@android:style/Widget.DeviceDefault.Light.MediaRouteButton</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar --> <style name="Theme.DeviceDefault.Light.NoActionBar" parent="Theme.Holo.Light.NoActionBar" > diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index b23443de2457..7e4ca6a17363 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -113,6 +113,7 @@ public class MediaRouter { mHandler = new Handler(mAppContext.getMainLooper()); mAudioManager = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE); + mSystemCategory = new RouteCategory(mAppContext.getText( com.android.internal.R.string.default_audio_route_category_name), ROUTE_TYPE_LIVE_AUDIO, false); @@ -151,9 +152,31 @@ public class MediaRouter { mDefaultAudio.mName = mAppContext.getText( com.android.internal.R.string.default_audio_route_name); mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO; + final int maxMusicVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + if (maxMusicVolume > 0) { + mDefaultAudio.mVolume = + mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC) / maxMusicVolume; + } addRoute(mDefaultAudio); } + /** + * @hide for use by framework routing UI + */ + public RouteInfo getSystemAudioRoute() { + return mDefaultAudio; + } + + /** + * Return the currently selected route for the given types + * + * @param type route types + * @return the selected route + */ + public RouteInfo getSelectedRoute(int type) { + return mSelectedRoute; + } + void onHeadphonesPlugged(boolean headphonesPresent, String headphonesName) { mDefaultAudio.mName = headphonesPresent ? headphonesName : mAppContext.getText( com.android.internal.R.string.default_audio_route_name); @@ -161,17 +184,18 @@ public class MediaRouter { } /** - * Set volume for the specified route types. + * Set volume for the specified selected route types. * * @param types Volume will be set for these route types * @param volume Volume to set in the range 0.f (inaudible) to 1.f (full volume). */ - public void setRouteVolume(int types, float volume) { - if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) { + public void setSelectedRouteVolume(int types, float volume) { + if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0 && mSelectedRoute == mDefaultAudio) { final int index = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, index, 0); } - if ((types & ROUTE_TYPE_USER) != 0) { + if ((types & ROUTE_TYPE_USER) != 0 && mSelectedRoute instanceof UserRouteInfo) { + mSelectedRoute.mVolume = volume; dispatchVolumeChanged(ROUTE_TYPE_USER, volume); } } @@ -267,6 +291,21 @@ public class MediaRouter { removeRoute(info); } + /** + * Remove all app-specified routes from the MediaRouter. + * + * @see #removeUserRoute(UserRouteInfo) + */ + public void clearUserRoutes() { + for (int i = 0; i < mRoutes.size(); i++) { + final RouteInfo info = mRoutes.get(i); + if (info instanceof UserRouteInfo) { + removeRouteAt(i); + i--; + } + } + } + void removeRoute(RouteInfo info) { if (mRoutes.remove(info)) { final RouteCategory removingCat = info.getCategory(); @@ -286,6 +325,26 @@ public class MediaRouter { } } + void removeRouteAt(int routeIndex) { + if (routeIndex >= 0 && routeIndex < mRoutes.size()) { + final RouteInfo info = mRoutes.remove(routeIndex); + final RouteCategory removingCat = info.getCategory(); + final int count = mRoutes.size(); + boolean found = false; + for (int i = 0; i < count; i++) { + final RouteCategory cat = mRoutes.get(i).getCategory(); + if (removingCat == cat) { + found = true; + break; + } + } + if (!found) { + mCategories.remove(removingCat); + } + dispatchRouteRemoved(info); + } + } + /** * Return the number of {@link MediaRouter.RouteCategory categories} currently * represented by routes known to this MediaRouter. @@ -437,6 +496,7 @@ public class MediaRouter { int mSupportedTypes; RouteGroup mGroup; final RouteCategory mCategory; + float mVolume; RouteInfo(RouteCategory category) { mCategory = category; @@ -480,6 +540,13 @@ public class MediaRouter { return mCategory; } + /** + * @return This route's current volume setting. + */ + public float getVolume() { + return mVolume; + } + void setStatusInt(CharSequence status) { if (!status.equals(mStatus)) { mStatus = status; @@ -828,6 +895,7 @@ public class MediaRouter { final int maxVol = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); final int volExtra = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); final float volume = (float) volExtra / maxVol; + mDefaultAudio.mVolume = volume; dispatchVolumeChanged(ROUTE_TYPE_LIVE_AUDIO, volume); } } |