Index: third_party/android_media/java/src/org/chromium/third_party/android/media/ChromeMediaRouteControllerDialog.java |
diff --git a/third_party/android_media/java/src/org/chromium/third_party/android/media/ChromeMediaRouteControllerDialog.java b/third_party/android_media/java/src/org/chromium/third_party/android/media/ChromeMediaRouteControllerDialog.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c9019d93280da7cdce87e36c8cea6ba07e880d7b |
--- /dev/null |
+++ b/third_party/android_media/java/src/org/chromium/third_party/android/media/ChromeMediaRouteControllerDialog.java |
@@ -0,0 +1,346 @@ |
+/* |
+ * Copyright (C) 2013 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 org.chromium.third_party.android.media; |
+ |
+import android.app.Dialog; |
+import android.content.Context; |
+import android.graphics.drawable.Drawable; |
+import android.os.Bundle; |
+import android.support.v7.media.MediaRouteSelector; |
+import android.support.v7.media.MediaRouter; |
+import android.support.v7.mediarouter.R; |
+import android.util.TypedValue; |
+import android.view.ContextThemeWrapper; |
+import android.view.KeyEvent; |
+import android.view.View; |
+import android.view.Window; |
+import android.widget.Button; |
+import android.widget.FrameLayout; |
+import android.widget.LinearLayout; |
+import android.widget.SeekBar; |
+ |
+ |
+ |
+/** |
+ * This class implements the route controller dialog for {@link MediaRouter}. |
+ * <p> |
+ * This dialog allows the user to control or disconnect from the currently selected route. |
+ * It differs from the default MediaRouteControllerDialog in that the click handler |
+ * for the disconnect button calls @code{DisconnectListener}. |
+ * </p> |
+ */ |
+public class ChromeMediaRouteControllerDialog extends Dialog { |
+ private static final String TAG = "ChromeMediaRouteControllerDialog"; |
+ |
+ private final MediaRouter mRouter; |
+ private final MediaRouterCallback mCallback; |
+ private final MediaRouter.RouteInfo mRoute; |
+ |
+ private boolean mCreated; |
+ private Drawable mMediaRouteConnectingDrawable; |
+ private Drawable mMediaRouteOnDrawable; |
+ private Drawable mCurrentIconDrawable; |
+ |
+ private boolean mVolumeControlEnabled = true; |
+ private LinearLayout mVolumeLayout; |
+ private SeekBar mVolumeSlider; |
+ private boolean mVolumeSliderTouched; |
+ |
+ private View mControlView; |
+ |
+ private Button mDisconnectButton; |
+ private DisconnectListener mDisconnectListener; |
+ |
+ /** |
+ * Define the behavior when clicking the disconnect button. |
+ */ |
+ public static interface DisconnectListener { |
+ void onDisconnect(); |
+ } |
+ |
+ private static final class MediaRouterThemeHelper { |
+ private MediaRouterThemeHelper() { |
+ } |
+ |
+ public static Context createThemedContext(Context context, boolean forceDark) { |
+ boolean isLightTheme = isLightTheme(context); |
+ if (isLightTheme && forceDark) { |
+ context = new ContextThemeWrapper(context, R.style.Theme_AppCompat); |
+ isLightTheme = false; |
+ } |
+ return new ContextThemeWrapper(context, isLightTheme ? |
+ R.style.Theme_MediaRouter_Light : R.style.Theme_MediaRouter); |
+ } |
+ |
+ public static int getThemeResource(Context context, int attr) { |
+ TypedValue value = new TypedValue(); |
+ return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0; |
+ } |
+ |
+ public static Drawable getThemeDrawable(Context context, int attr) { |
+ int res = getThemeResource(context, attr); |
+ return res != 0 ? context.getResources().getDrawable(res) : null; |
+ } |
+ |
+ private static boolean isLightTheme(Context context) { |
+ TypedValue value = new TypedValue(); |
+ return context.getTheme().resolveAttribute(R.attr.isLightTheme, value, true) |
+ && value.data != 0; |
+ } |
+ } |
+ |
+ public ChromeMediaRouteControllerDialog(Context context, |
+ DisconnectListener disconnectListener) { |
+ this(context, 0, disconnectListener); |
+ } |
+ |
+ private ChromeMediaRouteControllerDialog(Context context, int theme, |
+ DisconnectListener disconnectListener) { |
+ super(MediaRouterThemeHelper.createThemedContext(context, true), theme); |
+ assert disconnectListener != null; |
+ |
+ context = getContext(); |
+ |
+ mRouter = MediaRouter.getInstance(context); |
+ mCallback = new MediaRouterCallback(); |
+ mRoute = mRouter.getSelectedRoute(); |
+ mDisconnectListener = disconnectListener; |
+ } |
+ |
+ /** |
+ * Gets the route that this dialog is controlling. |
+ */ |
+ public MediaRouter.RouteInfo getRoute() { |
+ return mRoute; |
+ } |
+ |
+ /** |
+ * Provides the subclass an opportunity to create a view that will |
+ * be included within the body of the dialog to offer additional media controls |
+ * for the currently playing content. |
+ * |
+ * @param savedInstanceState The dialog's saved instance state. |
+ * @return The media control view, or null if none. |
+ */ |
+ public View onCreateMediaControlView(Bundle savedInstanceState) { |
+ return null; |
+ } |
+ |
+ /** |
+ * Gets the media control view that was created by {@link #onCreateMediaControlView(Bundle)}. |
+ * |
+ * @return The media control view, or null if none. |
+ */ |
+ public View getMediaControlView() { |
+ return mControlView; |
+ } |
+ |
+ /** |
+ * Sets whether to enable the volume slider and volume control using the volume keys |
+ * when the route supports it. |
+ * <p> |
+ * The default value is true. |
+ * </p> |
+ */ |
+ public void setVolumeControlEnabled(boolean enable) { |
+ if (mVolumeControlEnabled != enable) { |
+ mVolumeControlEnabled = enable; |
+ if (mCreated) { |
+ updateVolume(); |
+ } |
+ } |
+ } |
+ |
+ /** |
+ * Returns whether to enable the volume slider and volume control using the volume keys |
+ * when the route supports it. |
+ */ |
+ public boolean isVolumeControlEnabled() { |
+ return mVolumeControlEnabled; |
+ } |
+ |
+ @Override |
+ protected void onCreate(Bundle savedInstanceState) { |
+ super.onCreate(savedInstanceState); |
+ |
+ getWindow().requestFeature(Window.FEATURE_LEFT_ICON); |
+ |
+ setContentView(R.layout.mr_media_route_controller_dialog); |
+ |
+ mVolumeLayout = (LinearLayout)findViewById(R.id.media_route_volume_layout); |
+ mVolumeSlider = (SeekBar)findViewById(R.id.media_route_volume_slider); |
+ mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { |
+ @Override |
+ public void onStartTrackingTouch(SeekBar seekBar) { |
+ mVolumeSliderTouched = true; |
+ } |
+ |
+ @Override |
+ public void onStopTrackingTouch(SeekBar seekBar) { |
+ mVolumeSliderTouched = false; |
+ updateVolume(); |
+ } |
+ |
+ @Override |
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { |
+ if (fromUser) { |
+ mRoute.requestSetVolume(progress); |
+ } |
+ } |
+ }); |
+ |
+ mDisconnectButton = (Button)findViewById(R.id.media_route_disconnect_button); |
+ mDisconnectButton.setOnClickListener(new View.OnClickListener() { |
+ @Override |
+ public void onClick(View v) { |
+ if (mDisconnectListener != null) { |
+ mDisconnectListener.onDisconnect(); |
+ } |
+ dismiss(); |
+ } |
+ }); |
+ |
+ mCreated = true; |
+ if (update()) { |
+ mControlView = onCreateMediaControlView(savedInstanceState); |
+ FrameLayout controlFrame = |
+ (FrameLayout)findViewById(R.id.media_route_control_frame); |
+ if (mControlView != null) { |
+ controlFrame.addView(mControlView); |
+ controlFrame.setVisibility(View.VISIBLE); |
+ } else { |
+ controlFrame.setVisibility(View.GONE); |
+ } |
+ } |
+ } |
+ |
+ @Override |
+ public void onAttachedToWindow() { |
+ super.onAttachedToWindow(); |
+ |
+ mRouter.addCallback(MediaRouteSelector.EMPTY, mCallback, |
+ MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS); |
+ update(); |
+ } |
+ |
+ @Override |
+ public void onDetachedFromWindow() { |
+ mRouter.removeCallback(mCallback); |
+ |
+ super.onDetachedFromWindow(); |
+ } |
+ |
+ @Override |
+ public boolean onKeyDown(int keyCode, KeyEvent event) { |
+ if (isVolumeControlAvailable()) { |
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { |
+ mRoute.requestUpdateVolume(-1); |
+ return true; |
+ } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { |
+ mRoute.requestUpdateVolume(1); |
+ return true; |
+ } |
+ } |
+ return super.onKeyDown(keyCode, event); |
+ } |
+ |
+ @Override |
+ public boolean onKeyUp(int keyCode, KeyEvent event) { |
+ if (isVolumeControlAvailable()) { |
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN |
+ || keyCode == KeyEvent.KEYCODE_VOLUME_UP) { |
+ return true; |
+ } |
+ } |
+ return super.onKeyUp(keyCode, event); |
+ } |
+ |
+ private boolean update() { |
+ if (!mRoute.isSelected() || mRoute.isDefault()) { |
+ dismiss(); |
+ return false; |
+ } |
+ |
+ setTitle(mRoute.getName()); |
+ updateVolume(); |
+ |
+ Drawable icon = getIconDrawable(); |
+ if (icon != mCurrentIconDrawable) { |
+ mCurrentIconDrawable = icon; |
+ |
+ // There seems to be a bug in the framework where feature drawables |
+ // will not start animating unless they experience a transition from |
+ // invisible to visible. So we force the drawable to be invisible here. |
+ // The window will make the drawable visible when attached. |
+ icon.setVisible(false, true); |
+ getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, icon); |
+ } |
+ return true; |
+ } |
+ |
+ private Drawable getIconDrawable() { |
+ if (mRoute.isConnecting()) { |
+ if (mMediaRouteConnectingDrawable == null) { |
+ mMediaRouteConnectingDrawable = MediaRouterThemeHelper.getThemeDrawable( |
+ getContext(), R.attr.mediaRouteConnectingDrawable); |
+ } |
+ return mMediaRouteConnectingDrawable; |
+ } else { |
+ if (mMediaRouteOnDrawable == null) { |
+ mMediaRouteOnDrawable = MediaRouterThemeHelper.getThemeDrawable( |
+ getContext(), R.attr.mediaRouteOnDrawable); |
+ } |
+ return mMediaRouteOnDrawable; |
+ } |
+ } |
+ |
+ private void updateVolume() { |
+ if (!mVolumeSliderTouched) { |
+ if (isVolumeControlAvailable()) { |
+ mVolumeLayout.setVisibility(View.VISIBLE); |
+ mVolumeSlider.setMax(mRoute.getVolumeMax()); |
+ mVolumeSlider.setProgress(mRoute.getVolume()); |
+ } else { |
+ mVolumeLayout.setVisibility(View.GONE); |
+ } |
+ } |
+ } |
+ |
+ private boolean isVolumeControlAvailable() { |
+ return mVolumeControlEnabled && mRoute.getVolumeHandling() == |
+ MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE; |
+ } |
+ |
+ private final class MediaRouterCallback extends MediaRouter.Callback { |
+ @Override |
+ public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) { |
+ update(); |
+ } |
+ |
+ @Override |
+ public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) { |
+ update(); |
+ } |
+ |
+ @Override |
+ public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) { |
+ if (route == mRoute) { |
+ updateVolume(); |
+ } |
+ } |
+ } |
+} |