/* * 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 com.android.internal.app; import com.android.internal.R; import android.app.AlertDialog; import android.app.MediaRouteActionProvider; import android.app.MediaRouteButton; import android.content.Context; import android.content.DialogInterface; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; import android.media.MediaRouter; import android.media.MediaRouter.RouteGroup; import android.media.MediaRouter.RouteInfo; import android.os.Bundle; import android.util.TypedValue; import android.view.KeyEvent; import android.view.View; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.SeekBar; /** * This class implements the route controller dialog for {@link MediaRouter}. *

* This dialog allows the user to control or disconnect from the currently selected route. *

* * @see MediaRouteButton * @see MediaRouteActionProvider * * TODO: Move this back into the API, as in the support library media router. */ public class MediaRouteControllerDialog extends AlertDialog { // Time to wait before updating the volume when the user lets go of the seek bar // to allow the route provider time to propagate the change and publish a new // route descriptor. private static final int VOLUME_UPDATE_DELAY_MILLIS = 250; private final MediaRouter mRouter; private final MediaRouterCallback mCallback; private final MediaRouter.RouteInfo mRoute; private boolean mCreated; private Drawable mMediaRouteButtonDrawable; private int[] mMediaRouteConnectingState = { R.attr.state_checked, R.attr.state_enabled }; private int[] mMediaRouteOnState = { R.attr.state_activated, R.attr.state_enabled }; private Drawable mCurrentIconDrawable; private boolean mVolumeControlEnabled = true; private LinearLayout mVolumeLayout; private SeekBar mVolumeSlider; private boolean mVolumeSliderTouched; private View mControlView; private boolean mAttachedToWindow; public MediaRouteControllerDialog(Context context, int theme) { super(context, theme); mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); mCallback = new MediaRouterCallback(); mRoute = mRouter.getSelectedRoute(); } /** * 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. *

* The default value is true. *

*/ 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) { setTitle(mRoute.getName()); Resources res = getContext().getResources(); setButton(BUTTON_NEGATIVE, res.getString(R.string.media_route_controller_disconnect), new OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int id) { if (mRoute.isSelected()) { if (mRoute.isBluetooth()) { mRouter.getDefaultRoute().select(); } else { mRouter.getFallbackRoute().select(); } } dismiss(); } }); View customView = getLayoutInflater().inflate(R.layout.media_route_controller_dialog, null); setView(customView, 0, 0, 0, 0); super.onCreate(savedInstanceState); View customPanelView = getWindow().findViewById(R.id.customPanel); if (customPanelView != null) { customPanelView.setMinimumHeight(0); } mVolumeLayout = (LinearLayout) customView.findViewById(R.id.media_route_volume_layout); mVolumeSlider = (SeekBar) customView.findViewById(R.id.media_route_volume_slider); mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { private final Runnable mStopTrackingTouch = new Runnable() { @Override public void run() { if (mVolumeSliderTouched) { mVolumeSliderTouched = false; updateVolume(); } } }; @Override public void onStartTrackingTouch(SeekBar seekBar) { if (mVolumeSliderTouched) { mVolumeSlider.removeCallbacks(mStopTrackingTouch); } else { mVolumeSliderTouched = true; } } @Override public void onStopTrackingTouch(SeekBar seekBar) { // Defer resetting mVolumeSliderTouched to allow the media route provider // a little time to settle into its new state and publish the final // volume update. mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS); } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { mRoute.requestSetVolume(progress); } } }); mMediaRouteButtonDrawable = obtainMediaRouteButtonDrawable(); mCreated = true; if (update()) { mControlView = onCreateMediaControlView(savedInstanceState); FrameLayout controlFrame = (FrameLayout) customView.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(); mAttachedToWindow = true; mRouter.addCallback(0, mCallback, MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS); update(); } @Override public void onDetachedFromWindow() { mRouter.removeCallback(mCallback); mAttachedToWindow = false; super.onDetachedFromWindow(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) { mRoute.requestUpdateVolume(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? -1 : 1); return true; } return super.onKeyDown(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { 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; if (icon instanceof AnimationDrawable) { AnimationDrawable animDrawable = (AnimationDrawable) icon; if (!mAttachedToWindow && !mRoute.isConnecting()) { // When the route is already connected before the view is attached, show the // last frame of the connected animation immediately. if (animDrawable.isRunning()) { animDrawable.stop(); } icon = animDrawable.getFrame(animDrawable.getNumberOfFrames() - 1); } else if (!animDrawable.isRunning()) { animDrawable.start(); } } setIcon(icon); } return true; } private Drawable obtainMediaRouteButtonDrawable() { Context context = getContext(); TypedValue value = new TypedValue(); if (!context.getTheme().resolveAttribute(R.attr.mediaRouteButtonStyle, value, true)) { return null; } int[] drawableAttrs = new int[] { R.attr.externalRouteEnabledDrawable }; TypedArray a = context.obtainStyledAttributes(value.data, drawableAttrs); Drawable drawable = a.getDrawable(0); a.recycle(); return drawable; } private Drawable getIconDrawable() { if (!(mMediaRouteButtonDrawable instanceof StateListDrawable)) { return mMediaRouteButtonDrawable; } else if (mRoute.isConnecting()) { StateListDrawable stateListDrawable = (StateListDrawable) mMediaRouteButtonDrawable; stateListDrawable.setState(mMediaRouteConnectingState); return stateListDrawable.getCurrent(); } else { StateListDrawable stateListDrawable = (StateListDrawable) mMediaRouteButtonDrawable; stateListDrawable.setState(mMediaRouteOnState); return stateListDrawable.getCurrent(); } } 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.SimpleCallback { @Override public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { update(); } @Override public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) { update(); } @Override public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) { if (route == mRoute) { updateVolume(); } } @Override public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index) { update(); } @Override public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { update(); } } }