content: /*
   * 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 com.android.internal.app.MediaRouteDialogPresenter;
  
  import android.annotation.NonNull;
  import android.content.Context;
  import android.content.ContextWrapper;
  import android.content.res.TypedArray;
  import android.graphics.Canvas;
  import android.graphics.drawable.AnimationDrawable;
  import android.graphics.drawable.Drawable;
  import android.media.MediaRouter;
  import android.media.MediaRouter.RouteGroup;
  import android.media.MediaRouter.RouteInfo;
  import android.util.AttributeSet;
  import android.view.SoundEffectConstants;
  import android.view.View;
  
  public class MediaRouteButton extends View {
      private final MediaRouter mRouter;
      private final MediaRouterCallback mCallback;
  
      private int mRouteTypes;
  
      private boolean mAttachedToWindow;
  
      private Drawable mRemoteIndicator;
      private boolean mRemoteActive;
      private boolean mIsConnecting;
  
      private int mMinWidth;
      private int mMinHeight;
  
      private OnClickListener mExtendedSettingsClickListener;
  
      // 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
      };
  
      public MediaRouteButton(Context context) {
          this(context, null);
      }
  
      public MediaRouteButton(Context context, AttributeSet attrs) {
          this(context, attrs, com.android.internal.R.attr.mediaRouteButtonStyle);
      }
  
      public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
          this(context, attrs, defStyleAttr, 0);
      }
  
      public MediaRouteButton(
              Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
  
          mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
          mCallback = new MediaRouterCallback();
  
          final TypedArray a = context.obtainStyledAttributes(attrs,
                  com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, defStyleRes);
          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);
          final int routeTypes = a.getInteger(
                  com.android.internal.R.styleable.MediaRouteButton_mediaRouteTypes,
                  MediaRouter.ROUTE_TYPE_LIVE_AUDIO);
          a.recycle();
  
          setClickable(true);
  
          setRouteTypes(routeTypes);
      }
  
      /**
       * 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;
      }
  
      /**
       * 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);
              }
  
              mRouteTypes = types;
  
              if (mAttachedToWindow && types != 0) {
                  mRouter.addCallback(types, mCallback,
                          MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
              }
  
              refreshRoute();
          }
      }
  
      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;
          }
  
          DialogFragment f = MediaRouteDialogPresenter.showDialogFragment(getActivity(),
                  mRouteTypes, mExtendedSettingsClickListener);
          return f != null;
      }
  
      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.");
      }
  
      @Override
      public void setContentDescription(CharSequence contentDescription) {
          super.setContentDescription(contentDescription);
          setTooltipText(contentDescription);
      }
  
      @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
      protected int[] onCreateDrawableState(int extraSpace) {
          final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
  
          // Technically we should be handling this more completely, but these
          // are implementation details here. Checked is used to express the connecting
          // drawable state and it's mutually exclusive with activated for the purposes
          // of state selection here.
          if (mIsConnecting) {
              mergeDrawableStates(drawableState, CHECKED_STATE_SET);
          } else if (mRemoteActive) {
              mergeDrawableStates(drawableState, ACTIVATED_STATE_SET);
          }
          return drawableState;
      }
  
      @Override
      protected void drawableStateChanged() {
          super.drawableStateChanged();
  
          final Drawable remoteIndicator = mRemoteIndicator;
          if (remoteIndicator != null && remoteIndicator.isStateful()
                  && remoteIndicator.setState(getDrawableState())) {
              invalidateDrawable(remoteIndicator);
          }
      }
  
      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(@NonNull 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
      public void onAttachedToWindow() {
          super.onAttachedToWindow();
  
          mAttachedToWindow = true;
          if (mRouteTypes != 0) {
              mRouter.addCallback(mRouteTypes, mCallback,
                      MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
          }
          refreshRoute();
      }
  
      @Override
      public void onDetachedFromWindow() {
          mAttachedToWindow = false;
          if (mRouteTypes != 0) {
              mRouter.removeCallback(mCallback);
          }
  
          super.onDetachedFromWindow();
      }
  
      @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 width = Math.max(mMinWidth, mRemoteIndicator != null ?
                  mRemoteIndicator.getIntrinsicWidth() + getPaddingLeft() + getPaddingRight() : 0);
          final int height = Math.max(mMinHeight, mRemoteIndicator != null ?
                  mRemoteIndicator.getIntrinsicHeight() + getPaddingTop() + getPaddingBottom() : 0);
  
          int measuredWidth;
          switch (widthMode) {
              case MeasureSpec.EXACTLY:
                  measuredWidth = widthSize;
                  break;
              case MeasureSpec.AT_MOST:
                  measuredWidth = Math.min(widthSize, width);
                  break;
              default:
              case MeasureSpec.UNSPECIFIED:
                  measuredWidth = width;
                  break;
          }
  
          int measuredHeight;
          switch (heightMode) {
              case MeasureSpec.EXACTLY:
                  measuredHeight = heightSize;
                  break;
              case MeasureSpec.AT_MOST:
                  measuredHeight = Math.min(heightSize, height);
                  break;
              default:
              case MeasureSpec.UNSPECIFIED:
                  measuredHeight = height;
                  break;
          }
  
          setMeasuredDimension(measuredWidth, measuredHeight);
      }
  
      @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 void refreshRoute() {
          final MediaRouter.RouteInfo route = mRouter.getSelectedRoute();
          final boolean isRemote = !route.isDefault() && route.matchesTypes(mRouteTypes);
          final boolean isConnecting = isRemote && route.isConnecting();
          boolean needsRefresh = false;
          if (mRemoteActive != isRemote) {
              mRemoteActive = isRemote;
              needsRefresh = true;
          }
          if (mIsConnecting != isConnecting) {
              mIsConnecting = isConnecting;
              needsRefresh = true;
          }
  
          if (needsRefresh) {
              refreshDrawableState();
          }
          if (mAttachedToWindow) {
              setEnabled(mRouter.isRouteAvailable(mRouteTypes,
                      MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE));
          }
          if (mRemoteIndicator != null
                  && mRemoteIndicator.getCurrent() instanceof AnimationDrawable) {
              AnimationDrawable curDrawable = (AnimationDrawable) mRemoteIndicator.getCurrent();
              if (mAttachedToWindow) {
                  if ((needsRefresh || isConnecting) && !curDrawable.isRunning()) {
                      curDrawable.start();
                  }
              } else if (isRemote && !isConnecting) {
                  // When the route is already connected before the view is attached, show the last
                  // frame of the connected animation immediately.
                  if (curDrawable.isRunning()) {
                      curDrawable.stop();
                  }
                  curDrawable.selectDrawable(curDrawable.getNumberOfFrames() - 1);
              }
          }
      }
  
      private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
          @Override
          public void onRouteAdded(MediaRouter router, RouteInfo info) {
              refreshRoute();
          }
  
          @Override
          public void onRouteRemoved(MediaRouter router, RouteInfo info) {
              refreshRoute();
          }
  
          @Override
          public void onRouteChanged(MediaRouter router, RouteInfo info) {
              refreshRoute();
          }
  
          @Override
          public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
              refreshRoute();
          }
  
          @Override
          public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
              refreshRoute();
          }
  
          @Override
          public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
                  int index) {
              refreshRoute();
          }
  
          @Override
          public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
              refreshRoute();
          }
      }
  }
  