java.lang.Object
↳View
↳androidx.mediarouter.app.MediaRouteButton
Gradle dependencies
compile group: 'androidx.mediarouter', name: 'mediarouter', version: '1.3.0'
- groupId: androidx.mediarouter
- artifactId: mediarouter
- version: 1.3.0
Artifact androidx.mediarouter:mediarouter:1.3.0 it located at Google repository (https://maven.google.com/)
Androidx artifact mapping:
androidx.mediarouter:mediarouter com.android.support:mediarouter-v7
Androidx class mapping:
androidx.mediarouter.app.MediaRouteButton android.support.v7.app.MediaRouteButton
Overview
The media route button allows the user to select routes and to control the
currently selected route.
The application must specify the kinds of routes that the user should be allowed
to select by specifying a selector with the
MediaRouteButton.setRouteSelector(MediaRouteSelector) method.
When the default route is selected, the button will appear in an inactive state indicating
that the application is not connected to a route. Clicking on the button opens
a MediaRouteChooserDialog to allow the user to select a route.
If no non-default routes match the selector and it is not possible for an active
scan to discover any matching routes, then the button is disabled and cannot
be clicked unless MediaRouteButton.setAlwaysVisible(boolean) is called.
When a non-default route is selected, the button will appear in an active state indicating
that the application is connected to a route of the kind that it wants to use.
The button may also appear in an intermediary connecting state if the route is in the process
of connecting to the destination but has not yet completed doing so. In either case, clicking
on the button opens a MediaRouteControllerDialog to allow the user
to control or disconnect from the current route.
Prerequisites
To use the media route button, the activity must be a subclass of
FragmentActivity from the android.support.v4
support library. Refer to support library documentation for details.
Summary
Methods |
---|
protected void | drawableStateChanged()
|
public void | enableDynamicGroup()
Enables dynamic group feature. |
public MediaRouteDialogFactory | getDialogFactory()
Gets the media route dialog factory to use when showing the route chooser
or controller dialog. |
public MediaRouteSelector | getRouteSelector()
Gets the media route selector for filtering the routes that the user can
select using the media route chooser dialog. |
public void | jumpDrawablesToCurrentState()
|
public void | onAttachedToWindow()
|
protected int[] | onCreateDrawableState(int extraSpace)
|
public void | onDetachedFromWindow()
|
protected void | onDraw(Canvas canvas)
|
protected void | onMeasure(int widthMeasureSpec, int heightMeasureSpec)
|
public boolean | performClick()
|
public void | setAlwaysVisible(boolean alwaysVisible)
Sets whether the button is visible when no routes are available. |
public void | setDialogFactory(MediaRouteDialogFactory factory)
Sets the media route dialog factory to use when showing the route chooser
or controller dialog. |
public void | setRemoteIndicatorDrawable(Drawable d)
Sets a drawable to use as the remote route indicator. |
public void | setRouteSelector(MediaRouteSelector selector)
Sets the media route selector for filtering the routes that the user can
select using the media route chooser dialog. |
public void | setVisibility(int visibility)
|
public boolean | showDialog()
Show the route chooser or controller dialog. |
protected boolean | verifyDrawable(Drawable who)
|
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Constructors
public
MediaRouteButton(Context context)
public
MediaRouteButton(Context context, AttributeSet attrs)
public
MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr)
Methods
Gets the media route selector for filtering the routes that the user can
select using the media route chooser dialog.
Returns:
The selector, never null.
Sets the media route selector for filtering the routes that the user can
select using the media route chooser dialog.
Parameters:
selector: The selector, must not be null.
Gets the media route dialog factory to use when showing the route chooser
or controller dialog.
Returns:
The dialog factory, never null.
Sets the media route dialog factory to use when showing the route chooser
or controller dialog.
Parameters:
factory: The dialog factory, must not be null.
public void
enableDynamicGroup()
Deprecated: Use MediaRouterParams.Builder.setDialogType(int) with
MediaRouterParams.DIALOG_TYPE_DYNAMIC_GROUP instead.
Enables dynamic group feature.
With this enabled, a different set of MediaRouteChooserDialog and
MediaRouteControllerDialog is shown when the button is clicked.
If a media route provider
supports dynamic group, the users can use that feature with the dialogs.
See also: MediaRouteProvider.DynamicGroupRouteController
public boolean
showDialog()
Show the route chooser or controller dialog.
If the default route is selected, 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.
Dialog types can be set by setting MediaRouterParams to the router.
The application can customize the dialogs by calling MediaRouteButton.setDialogFactory(MediaRouteDialogFactory)
to provide a customized dialog factory.
Returns:
True if the dialog was actually shown.
See also:
public boolean
performClick()
protected int[]
onCreateDrawableState(int extraSpace)
protected void
drawableStateChanged()
public void
setRemoteIndicatorDrawable(Drawable d)
Sets a drawable to use as the remote route indicator.
public void
setAlwaysVisible(boolean alwaysVisible)
Sets whether the button is visible when no routes are available.
When true, the button is visible even when there are no routes to connect.
You may want to override View
to change the behavior
when the button is clicked.
The default is false.
It doesn't overrides the visibility
status of the button.
Parameters:
alwaysVisible: true to show the button even when no routes are available.
protected boolean
verifyDrawable(Drawable who)
public void
jumpDrawablesToCurrentState()
public void
setVisibility(int visibility)
public void
onAttachedToWindow()
public void
onDetachedFromWindow()
protected void
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
protected void
onDraw(Canvas canvas)
Source
/*
* Copyright 2018 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 androidx.mediarouter.app;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.SoundEffectConstants;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.TooltipCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.mediarouter.R;
import androidx.mediarouter.media.MediaRouteSelector;
import androidx.mediarouter.media.MediaRouter;
import androidx.mediarouter.media.MediaRouterParams;
import java.util.ArrayList;
import java.util.List;
/**
* The media route button allows the user to select routes and to control the
* currently selected route.
* <p>
* The application must specify the kinds of routes that the user should be allowed
* to select by specifying a {@link MediaRouteSelector selector} with the
* {@link #setRouteSelector} method.
* </p><p>
* When the default route is selected, the button will appear in an inactive state indicating
* that the application is not connected to a route. Clicking on the button opens
* a {@link MediaRouteChooserDialog} to allow the user to select a route.
* If no non-default routes match the selector and it is not possible for an active
* scan to discover any matching routes, then the button is disabled and cannot
* be clicked unless {@link #setAlwaysVisible} is called.
* </p><p>
* When a non-default route is selected, the button will appear in an active state indicating
* that the application is connected to a route of the kind that it wants to use.
* The button may also appear in an intermediary connecting state if the route is in the process
* of connecting to the destination but has not yet completed doing so. In either case, clicking
* on the button opens a {@link MediaRouteControllerDialog} to allow the user
* to control or disconnect from the current route.
* </p>
*
* <h3>Prerequisites</h3>
* <p>
* To use the media route button, the activity must be a subclass of
* {@link FragmentActivity} from the <code>android.support.v4</code>
* support library. Refer to support library documentation for details.
* </p>
*
* @see MediaRouteActionProvider
* @see #setRouteSelector
*/
public class MediaRouteButton extends View {
private static final String TAG = "MediaRouteButton";
private static final String CHOOSER_FRAGMENT_TAG =
"android.support.v7.mediarouter:MediaRouteChooserDialogFragment";
private static final String CONTROLLER_FRAGMENT_TAG =
"android.support.v7.mediarouter:MediaRouteControllerDialogFragment";
// Used to check connectivity and hide the button
private static ConnectivityReceiver sConnectivityReceiver;
private final MediaRouter mRouter;
private final MediaRouterCallback mCallback;
private MediaRouteSelector mSelector = MediaRouteSelector.EMPTY;
private MediaRouteDialogFactory mDialogFactory = MediaRouteDialogFactory.getDefault();
private boolean mAttachedToWindow;
private int mVisibility = VISIBLE;
@SuppressWarnings("WeakerAccess") /* synthetic access */
boolean mIsFixedIcon;
static final SparseArray<Drawable.ConstantState> sRemoteIndicatorCache =
new SparseArray<>(2);
RemoteIndicatorLoader mRemoteIndicatorLoader;
private Drawable mRemoteIndicator;
// The resource id to be lazily loaded, 0 if it doesn't need to be loaded.
private int mRemoteIndicatorResIdToLoad;
private static final int CONNECTION_STATE_DISCONNECTED =
MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED;
private static final int CONNECTION_STATE_CONNECTING =
MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTING;
private static final int CONNECTION_STATE_CONNECTED =
MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED;
private int mLastConnectionState;
private int mConnectionState;
private ColorStateList mButtonTint;
private int mMinWidth;
private int mMinHeight;
private boolean mAlwaysVisible;
private boolean mCheatSheetEnabled;
// The checked state is used when connected to a remote route.
private static final int[] CHECKED_STATE_SET = {
android.R.attr.state_checked
};
// The checkable state is used while connecting to a remote route.
private static final int[] CHECKABLE_STATE_SET = {
android.R.attr.state_checkable
};
public MediaRouteButton(@NonNull Context context) {
this(context, null);
}
public MediaRouteButton(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.mediaRouteButtonStyle);
}
public MediaRouteButton(@NonNull Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(MediaRouterThemeHelper.createThemedButtonContext(context), attrs, defStyleAttr);
context = getContext();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.MediaRouteButton, defStyleAttr, 0);
ViewCompat.saveAttributeDataForStyleable(
this, context, R.styleable.MediaRouteButton, attrs, a, defStyleAttr, 0);
if (isInEditMode()) {
mRouter = null;
mCallback = null;
int remoteIndicatorStaticResId = a.getResourceId(
R.styleable.MediaRouteButton_externalRouteEnabledDrawableStatic, 0);
mRemoteIndicator = AppCompatResources.getDrawable(context, remoteIndicatorStaticResId);
return;
}
mRouter = MediaRouter.getInstance(context);
mCallback = new MediaRouterCallback();
MediaRouter.RouteInfo selectedRoute = mRouter.getSelectedRoute();
boolean isRemote = !selectedRoute.isDefaultOrBluetooth();
mLastConnectionState = mConnectionState =
(isRemote ? selectedRoute.getConnectionState() : CONNECTION_STATE_DISCONNECTED);
if (sConnectivityReceiver == null) {
sConnectivityReceiver = new ConnectivityReceiver(context.getApplicationContext());
}
mButtonTint = a.getColorStateList(R.styleable.MediaRouteButton_mediaRouteButtonTint);
mMinWidth = a.getDimensionPixelSize(
R.styleable.MediaRouteButton_android_minWidth, 0);
mMinHeight = a.getDimensionPixelSize(
R.styleable.MediaRouteButton_android_minHeight, 0);
int remoteIndicatorStaticResId = a.getResourceId(
R.styleable.MediaRouteButton_externalRouteEnabledDrawableStatic, 0);
mRemoteIndicatorResIdToLoad = a.getResourceId(
R.styleable.MediaRouteButton_externalRouteEnabledDrawable, 0);
a.recycle();
if (mRemoteIndicatorResIdToLoad != 0) {
Drawable.ConstantState remoteIndicatorState =
sRemoteIndicatorCache.get(mRemoteIndicatorResIdToLoad);
if (remoteIndicatorState != null) {
setRemoteIndicatorDrawable(remoteIndicatorState.newDrawable());
}
}
if (mRemoteIndicator == null) {
if (remoteIndicatorStaticResId != 0) {
Drawable.ConstantState remoteIndicatorStaticState =
sRemoteIndicatorCache.get(remoteIndicatorStaticResId);
if (remoteIndicatorStaticState != null) {
setRemoteIndicatorDrawableInternal(remoteIndicatorStaticState.newDrawable());
} else {
mRemoteIndicatorLoader = new RemoteIndicatorLoader(remoteIndicatorStaticResId,
getContext());
mRemoteIndicatorLoader.executeOnExecutor(android.os.AsyncTask.SERIAL_EXECUTOR);
}
} else {
loadRemoteIndicatorIfNeeded();
}
}
updateContentDescription();
setClickable(true);
}
/**
* Gets the media route selector for filtering the routes that the user can
* select using the media route chooser dialog.
*
* @return The selector, never null.
*/
@NonNull
public MediaRouteSelector getRouteSelector() {
return mSelector;
}
/**
* Sets the media route selector for filtering the routes that the user can
* select using the media route chooser dialog.
*
* @param selector The selector, must not be null.
*/
public void setRouteSelector(@NonNull MediaRouteSelector selector) {
if (selector == null) {
throw new IllegalArgumentException("selector must not be null");
}
if (!mSelector.equals(selector)) {
if (mAttachedToWindow) {
if (!mSelector.isEmpty()) {
mRouter.removeCallback(mCallback);
}
if (!selector.isEmpty()) {
mRouter.addCallback(selector, mCallback);
}
}
mSelector = selector;
refreshRoute();
}
}
/**
* Gets the media route dialog factory to use when showing the route chooser
* or controller dialog.
*
* @return The dialog factory, never null.
*/
@NonNull
public MediaRouteDialogFactory getDialogFactory() {
return mDialogFactory;
}
/**
* Sets the media route dialog factory to use when showing the route chooser
* or controller dialog.
*
* @param factory The dialog factory, must not be null.
*/
public void setDialogFactory(@NonNull MediaRouteDialogFactory factory) {
if (factory == null) {
throw new IllegalArgumentException("factory must not be null");
}
mDialogFactory = factory;
}
/**
* Enables dynamic group feature.
* With this enabled, a different set of {@link MediaRouteChooserDialog} and
* {@link MediaRouteControllerDialog} is shown when the button is clicked.
* If a {@link androidx.mediarouter.media.MediaRouteProvider media route provider}
* supports dynamic group, the users can use that feature with the dialogs.
*
* @see androidx.mediarouter.media.MediaRouteProvider.DynamicGroupRouteController
*
* @deprecated Use {@link
* androidx.mediarouter.media.MediaRouterParams.Builder#setDialogType(int)} with
* {@link androidx.mediarouter.media.MediaRouterParams#DIALOG_TYPE_DYNAMIC_GROUP} instead.
*/
@Deprecated
public void enableDynamicGroup() {
MediaRouterParams oldParams = mRouter.getRouterParams();
MediaRouterParams.Builder newParamsBuilder = oldParams == null
? new MediaRouterParams.Builder() : new MediaRouterParams.Builder(oldParams);
newParamsBuilder.setDialogType(MediaRouterParams.DIALOG_TYPE_DYNAMIC_GROUP);
mRouter.setRouterParams(newParamsBuilder.build());
}
/**
* Show the route chooser or controller dialog.
* <p>
* If the default route is selected, 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>
* Dialog types can be set by setting {@link MediaRouterParams} to the router.
* <p>
* The application can customize the dialogs by calling {@link #setDialogFactory}
* to provide a customized dialog factory.
* <p>
*
* @return True if the dialog was actually shown.
*
* @throws IllegalStateException if the activity is not a subclass of
* {@link FragmentActivity}.
*
* @see MediaRouterParams.Builder#setDialogType(int)
* @see MediaRouterParams.Builder#setOutputSwitcherEnabled(boolean)
*/
public boolean showDialog() {
if (!mAttachedToWindow) {
return false;
}
MediaRouterParams params = mRouter.getRouterParams();
if (params != null) {
if (params.isOutputSwitcherEnabled() && MediaRouter.isMediaTransferEnabled()) {
if (showOutputSwitcher()) {
// Output switcher is successfully shown.
return true;
}
}
int dialogType = params.getDialogType();
return showDialogForType(dialogType);
} else {
// Note: These apps didn't call enableDynamicGroup(), since calling the method
// automatically sets a MediaRouterParams with dynamic dialog type.
return showDialogForType(MediaRouterParams.DIALOG_TYPE_DEFAULT);
}
}
private boolean showDialogForType(@MediaRouterParams.DialogType int dialogType) {
final FragmentManager fm = getFragmentManager();
if (fm == null) {
throw new IllegalStateException("The activity must be a subclass of FragmentActivity");
}
MediaRouter.RouteInfo selectedRoute = mRouter.getSelectedRoute();
if (selectedRoute.isDefaultOrBluetooth()) {
if (fm.findFragmentByTag(CHOOSER_FRAGMENT_TAG) != null) {
Log.w(TAG, "showDialog(): Route chooser dialog already showing!");
return false;
}
MediaRouteChooserDialogFragment f =
mDialogFactory.onCreateChooserDialogFragment();
f.setRouteSelector(mSelector);
if (dialogType == MediaRouterParams.DIALOG_TYPE_DYNAMIC_GROUP) {
f.setUseDynamicGroup(true);
}
FragmentTransaction transaction = fm.beginTransaction();
transaction.add(f, CHOOSER_FRAGMENT_TAG);
transaction.commitAllowingStateLoss();
} else {
if (fm.findFragmentByTag(CONTROLLER_FRAGMENT_TAG) != null) {
Log.w(TAG, "showDialog(): Route controller dialog already showing!");
return false;
}
MediaRouteControllerDialogFragment f =
mDialogFactory.onCreateControllerDialogFragment();
f.setRouteSelector(mSelector);
if (dialogType == MediaRouterParams.DIALOG_TYPE_DYNAMIC_GROUP) {
f.setUseDynamicGroup(true);
}
FragmentTransaction transaction = fm.beginTransaction();
transaction.add(f, CONTROLLER_FRAGMENT_TAG);
transaction.commitAllowingStateLoss();
}
return true;
}
/**
* Shows output switcher dialog. Returns {@code true} if it is successfully shown.
* Returns {@code false} if there was no output switcher.
*/
private boolean showOutputSwitcher() {
boolean result = false;
if (Build.VERSION.SDK_INT >= 31) {
result = showOutputSwitcherForAndroidSAndAbove();
if (!result) {
// The intent action and related string constants are changed in S,
// however they are not public API yet. Try opening the output switcher with the
// old constants for devices that have prior version of the constants.
result = showOutputSwitcherForAndroidR();
}
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
result = showOutputSwitcherForAndroidR();
}
return result;
}
@SuppressWarnings("deprecation")
private boolean showOutputSwitcherForAndroidR() {
Context context = getContext();
Intent intent = new Intent()
.setAction("com.android.settings.panel.action.MEDIA_OUTPUT")
.putExtra("com.android.settings.panel.extra.PACKAGE_NAME", context.getPackageName())
.putExtra("key_media_session_token", mRouter.getMediaSessionToken());
PackageManager packageManager = context.getPackageManager();
List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, 0);
for (ResolveInfo resolveInfo : resolveInfos) {
ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null || activityInfo.applicationInfo == null) {
continue;
}
ApplicationInfo appInfo = activityInfo.applicationInfo;
if (((ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)
& appInfo.flags) != 0) {
context.startActivity(intent);
return true;
}
}
return false;
}
private boolean showOutputSwitcherForAndroidSAndAbove() {
Context context = getContext();
Intent intent = new Intent()
.setAction("com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG")
.setPackage("com.android.systemui")
.putExtra("package_name", context.getPackageName())
.putExtra("key_media_session_token", mRouter.getMediaSessionToken());
PackageManager packageManager = context.getPackageManager();
List<ResolveInfo> resolveInfos = packageManager.queryBroadcastReceivers(intent, 0);
for (ResolveInfo resolveInfo : resolveInfos) {
ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null || activityInfo.applicationInfo == null) {
continue;
}
ApplicationInfo appInfo = activityInfo.applicationInfo;
if (((ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)
& appInfo.flags) != 0) {
context.sendBroadcast(intent);
return true;
}
}
return false;
}
private FragmentManager getFragmentManager() {
Activity activity = getActivity();
if (activity instanceof FragmentActivity) {
return ((FragmentActivity)activity).getSupportFragmentManager();
}
return 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();
}
return null;
}
/**
* Sets whether to enable showing a toast with the content descriptor of the
* button when the button is long pressed.
*/
void setCheatSheetEnabled(boolean enable) {
if (enable != mCheatSheetEnabled) {
mCheatSheetEnabled = enable;
updateContentDescription();
}
}
@Override
public boolean performClick() {
// Send the appropriate accessibility events and call listeners
boolean handled = super.performClick();
if (!handled) {
playSoundEffect(SoundEffectConstants.CLICK);
}
loadRemoteIndicatorIfNeeded();
return showDialog() || handled;
}
@Override
@NonNull
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. Checkable is used to express the connecting
// drawable state and it's mutually exclusive with check for the purposes
// of state selection here.
if (mRouter == null) {
return drawableState;
}
if (mIsFixedIcon) {
return drawableState;
}
switch (mConnectionState) {
case CONNECTION_STATE_CONNECTING:
mergeDrawableStates(drawableState, CHECKABLE_STATE_SET);
break;
case CONNECTION_STATE_CONNECTED:
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
break;
}
return drawableState;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mRemoteIndicator != null) {
int[] myDrawableState = getDrawableState();
mRemoteIndicator.setState(myDrawableState);
// When DrawableContainer#selectDrawable is called, the selected drawable is reset.
// We may need to start the animation or adjust the frame.
if (mRemoteIndicator.getCurrent() instanceof AnimationDrawable) {
AnimationDrawable curDrawable = (AnimationDrawable) mRemoteIndicator.getCurrent();
if (mConnectionState == CONNECTION_STATE_CONNECTING
|| mLastConnectionState != mConnectionState) {
if (!curDrawable.isRunning()) {
curDrawable.start();
}
} else {
// Assuming the last animation of the "connected" animation drawable
// shows "connected" static drawable.
if (mConnectionState == CONNECTION_STATE_CONNECTED
&& !curDrawable.isRunning()) {
curDrawable.selectDrawable(curDrawable.getNumberOfFrames() - 1);
}
}
}
invalidate();
}
mLastConnectionState = mConnectionState;
}
/**
* Sets a drawable to use as the remote route indicator.
*/
public void setRemoteIndicatorDrawable(@Nullable Drawable d) {
// to prevent overwriting user-set drawables
mRemoteIndicatorResIdToLoad = 0;
setRemoteIndicatorDrawableInternal(d);
}
/**
* Sets whether the button is visible when no routes are available.
* When true, the button is visible even when there are no routes to connect.
* You may want to override {@link View#performClick()} to change the behavior
* when the button is clicked.
* The default is false.
* It doesn't overrides the {@link View#getVisibility visibility} status of the button.
*
* @param alwaysVisible true to show the button even when no routes are available.
*/
public void setAlwaysVisible(boolean alwaysVisible) {
if (alwaysVisible != mAlwaysVisible) {
mAlwaysVisible = alwaysVisible;
refreshVisibility();
refreshRoute();
}
}
@Override
protected boolean verifyDrawable(@NonNull Drawable who) {
return super.verifyDrawable(who) || who == mRemoteIndicator;
}
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
// Handle our own remote indicator.
if (mRemoteIndicator != null) {
mRemoteIndicator.jumpToCurrentState();
}
}
@Override
public void setVisibility(int visibility) {
mVisibility = visibility;
refreshVisibility();
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
if (isInEditMode()) {
return;
}
mAttachedToWindow = true;
if (!mSelector.isEmpty()) {
mRouter.addCallback(mSelector, mCallback);
}
refreshRoute();
sConnectivityReceiver.registerReceiver(this);
}
@Override
public void onDetachedFromWindow() {
if (!isInEditMode()) {
mAttachedToWindow = false;
if (!mSelector.isEmpty()) {
mRouter.removeCallback(mCallback);
}
sConnectivityReceiver.unregisterReceiver(this);
}
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(@NonNull Canvas canvas) {
super.onDraw(canvas);
if (mRemoteIndicator != null) {
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 loadRemoteIndicatorIfNeeded() {
if (mRemoteIndicatorResIdToLoad > 0) {
if (mRemoteIndicatorLoader != null) {
mRemoteIndicatorLoader.cancel(false);
}
mRemoteIndicatorLoader = new RemoteIndicatorLoader(mRemoteIndicatorResIdToLoad,
getContext());
mRemoteIndicatorResIdToLoad = 0;
mRemoteIndicatorLoader.executeOnExecutor(android.os.AsyncTask.SERIAL_EXECUTOR);
}
}
void setRemoteIndicatorDrawableInternal(Drawable d) {
if (mRemoteIndicatorLoader != null) {
mRemoteIndicatorLoader.cancel(false);
}
if (mRemoteIndicator != null) {
mRemoteIndicator.setCallback(null);
unscheduleDrawable(mRemoteIndicator);
}
if (d != null) {
if (mButtonTint != null) {
d = DrawableCompat.wrap(d.mutate());
DrawableCompat.setTintList(d, mButtonTint);
}
d.setCallback(this);
d.setState(getDrawableState());
d.setVisible(getVisibility() == VISIBLE, false);
}
mRemoteIndicator = d;
refreshDrawableState();
}
void refreshVisibility() {
super.setVisibility(mVisibility == VISIBLE
&& !(mAlwaysVisible || sConnectivityReceiver.isConnected())
? INVISIBLE : mVisibility);
if (mRemoteIndicator != null) {
mRemoteIndicator.setVisible(getVisibility() == VISIBLE, false);
}
}
void refreshRoute() {
final MediaRouter.RouteInfo route = mRouter.getSelectedRoute();
final boolean isRemote = !route.isDefaultOrBluetooth();
final int connectionState = (isRemote ? route.getConnectionState()
: CONNECTION_STATE_DISCONNECTED);
if (mConnectionState != connectionState) {
mConnectionState = connectionState;
updateContentDescription();
refreshDrawableState();
}
if (connectionState == CONNECTION_STATE_CONNECTING) {
loadRemoteIndicatorIfNeeded();
}
if (mAttachedToWindow) {
setEnabled(mAlwaysVisible || isRemote || mRouter.isRouteAvailable(mSelector,
MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE));
}
}
private void updateContentDescription() {
int resId;
switch (mConnectionState) {
case CONNECTION_STATE_CONNECTING:
resId = R.string.mr_cast_button_connecting;
break;
case CONNECTION_STATE_CONNECTED:
resId = R.string.mr_cast_button_connected;
break;
default:
resId = R.string.mr_cast_button_disconnected;
break;
}
String contentDesc = getContext().getString(resId);
setContentDescription(contentDesc);
TooltipCompat.setTooltipText(this,
mCheatSheetEnabled && !TextUtils.isEmpty(contentDesc) ? contentDesc : null);
}
private final class MediaRouterCallback extends MediaRouter.Callback {
MediaRouterCallback() {
}
@Override
public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
refreshRoute();
}
@Override
public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
refreshRoute();
}
@Override
public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
refreshRoute();
}
@Override
public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo info) {
refreshRoute();
}
@Override
public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo info) {
refreshRoute();
}
@Override
public void onProviderAdded(MediaRouter router, MediaRouter.ProviderInfo provider) {
refreshRoute();
}
@Override
public void onProviderRemoved(MediaRouter router, MediaRouter.ProviderInfo provider) {
refreshRoute();
}
@Override
public void onProviderChanged(MediaRouter router, MediaRouter.ProviderInfo provider) {
refreshRoute();
}
@Override
public void onRouterParamsChanged(MediaRouter router, MediaRouterParams params) {
boolean fixedIcon = false;
if (params != null) {
fixedIcon = params.getExtras()
.getBoolean(MediaRouterParams.EXTRAS_KEY_FIXED_CAST_ICON);
}
if (MediaRouteButton.this.mIsFixedIcon != fixedIcon) {
MediaRouteButton.this.mIsFixedIcon = fixedIcon;
refreshDrawableState();
}
}
}
private final class RemoteIndicatorLoader extends android.os.AsyncTask<Void, Void, Drawable> {
private final int mResId;
private final Context mContext;
RemoteIndicatorLoader(int resId, Context context) {
mResId = resId;
mContext = context;
}
@Override
protected Drawable doInBackground(Void... params) {
Drawable.ConstantState remoteIndicatorState = sRemoteIndicatorCache.get(mResId);
if (remoteIndicatorState == null) {
return AppCompatResources.getDrawable(mContext, mResId);
} else {
return null;
}
}
@Override
protected void onPostExecute(Drawable remoteIndicator) {
if (remoteIndicator != null) {
cacheAndReset(remoteIndicator);
} else {
Drawable.ConstantState remoteIndicatorState = sRemoteIndicatorCache.get(mResId);
if (remoteIndicatorState != null) {
remoteIndicator = remoteIndicatorState.newDrawable();
}
mRemoteIndicatorLoader = null;
}
setRemoteIndicatorDrawableInternal(remoteIndicator);
}
@Override
protected void onCancelled(Drawable remoteIndicator) {
cacheAndReset(remoteIndicator);
}
private void cacheAndReset(Drawable remoteIndicator) {
if (remoteIndicator != null) {
sRemoteIndicatorCache.put(mResId, remoteIndicator.getConstantState());
}
mRemoteIndicatorLoader = null;
}
}
private static final class ConnectivityReceiver extends BroadcastReceiver {
private final Context mContext;
// If we have no information, assume that the device is connected
private boolean mIsConnected = true;
private List<MediaRouteButton> mButtons;
ConnectivityReceiver(Context context) {
mContext = context;
mButtons = new ArrayList<MediaRouteButton>();
}
public void registerReceiver(MediaRouteButton button) {
if (mButtons.size() == 0) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
mContext.registerReceiver(this, intentFilter);
}
mButtons.add(button);
}
public void unregisterReceiver(MediaRouteButton button) {
mButtons.remove(button);
if (mButtons.size() == 0) {
mContext.unregisterReceiver(this);
}
}
public boolean isConnected() {
return mIsConnected;
}
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
boolean isConnected = !intent.getBooleanExtra(
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
if (mIsConnected != isConnected) {
mIsConnected = isConnected;
for (MediaRouteButton button: mButtons) {
button.refreshVisibility();
}
}
}
}
}
}