public class

MediaRouteDynamicChooserDialog

extends AppCompatDialog

 java.lang.Object

↳ComponentDialog

androidx.appcompat.app.AppCompatDialog

↳androidx.mediarouter.app.MediaRouteDynamicChooserDialog

Gradle dependencies

compile group: 'androidx.mediarouter', name: 'mediarouter', version: '1.7.0'

  • groupId: androidx.mediarouter
  • artifactId: mediarouter
  • version: 1.7.0

Artifact androidx.mediarouter:mediarouter:1.7.0 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.mediarouter:mediarouter com.android.support:mediarouter-v7

Overview

This class implements the route device picker dialog for MediaRouter.

This dialog allows the user to choose a route that matches a given selector.

Summary

Constructors
publicMediaRouteDynamicChooserDialog(Context context)

publicMediaRouteDynamicChooserDialog(Context context, int theme)

Methods
public MediaRouteSelectorgetRouteSelector()

Gets the media route selector for filtering the routes that the user can select.

public voidonAttachedToWindow()

protected voidonCreate(Bundle savedInstanceState)

public voidonDetachedFromWindow()

public booleanonFilterRoute(MediaRouter.RouteInfo route)

Called to filter a specific route.

public voidonFilterRoutes(java.util.List<MediaRouter.RouteInfo> routes)

Called to filter the set of routes that should be included in the list.

public voidrefreshRoutes()

Refreshes the list of routes that are shown in the device picker dialog.

public voidsetRouteSelector(MediaRouteSelector selector)

Sets the media route selector for filtering the routes that the user can select.

from AppCompatDialogaddContentView, dismiss, dispatchKeyEvent, findViewById, getDelegate, getSupportActionBar, invalidateOptionsMenu, onStop, onSupportActionModeFinished, onSupportActionModeStarted, onWindowStartingSupportActionMode, setContentView, setContentView, setContentView, setTitle, setTitle, supportRequestWindowFeature
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public MediaRouteDynamicChooserDialog(Context context)

public MediaRouteDynamicChooserDialog(Context context, int theme)

Methods

public MediaRouteSelector getRouteSelector()

Gets the media route selector for filtering the routes that the user can select.

Returns:

The selector, never null.

public void setRouteSelector(MediaRouteSelector selector)

Sets the media route selector for filtering the routes that the user can select.

Parameters:

selector: The selector, must not be null.

public void onFilterRoutes(java.util.List<MediaRouter.RouteInfo> routes)

Called to filter the set of routes that should be included in the list.

The default implementation iterates over all routes in the provided list and removes those for which MediaRouteDynamicChooserDialog.onFilterRoute(MediaRouter.RouteInfo) returns false.

Parameters:

routes: The list of routes to filter in-place, never null.

public boolean onFilterRoute(MediaRouter.RouteInfo route)

Called to filter a specific route. Returns true to include in the picker dialog

The default implementation returns true for enabled non-default routes that match the selector. Subclasses can override this method to filter routes differently.

Parameters:

route: The route to consider, never null.

Returns:

true if the route should be included in the device picker dialog.

protected void onCreate(Bundle savedInstanceState)

public void onAttachedToWindow()

public void onDetachedFromWindow()

public void refreshRoutes()

Refreshes the list of routes that are shown in the device picker dialog.

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 static androidx.annotation.RestrictTo.Scope.LIBRARY;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;

import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.appcompat.app.AppCompatDialog;
import androidx.mediarouter.R;
import androidx.mediarouter.media.MediaRouteSelector;
import androidx.mediarouter.media.MediaRouter;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * This class implements the route device picker dialog for {@link MediaRouter}.
 * <p>
 * This dialog allows the user to choose a route that matches a given selector.
 *
 * @see MediaRouteButton
 * @see MediaRouteActionProvider
 */
@RestrictTo(LIBRARY)
public class MediaRouteDynamicChooserDialog extends AppCompatDialog {

    private static final int ITEM_TYPE_HEADER = 1;
    private static final int ITEM_TYPE_ROUTE = 2;

    // Do not update the route list immediately to avoid unnatural dialog change.
    private static final int MSG_UPDATE_ROUTES = 1;

    final MediaRouter mRouter;
    private final MediaRouteDynamicChooserDialog.MediaRouterCallback mCallback;

    Context mContext;
    private MediaRouteSelector mSelector = MediaRouteSelector.EMPTY;
    List<MediaRouter.RouteInfo> mRoutes;
    private ImageButton mCloseButton;
    private RecyclerAdapter mAdapter;
    private RecyclerView mRecyclerView;
    private boolean mAttachedToWindow;
    MediaRouter.RouteInfo mSelectingRoute;
    private long mUpdateRoutesDelayMs;
    private long mLastUpdateTime;
    @SuppressWarnings({"unchecked", "deprecation"})
    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message message) {
            switch (message.what) {
                case MSG_UPDATE_ROUTES:
                    updateRoutes((List<MediaRouter.RouteInfo>) message.obj);
                    break;
            }
        }
    };

    public MediaRouteDynamicChooserDialog(@NonNull Context context) {
        this(context, 0);
    }

    public MediaRouteDynamicChooserDialog(@NonNull Context context, int theme) {
        super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, false),
                MediaRouterThemeHelper.createThemedDialogStyle(context));
        context = getContext();

        mRouter = MediaRouter.getInstance(context);
        mCallback = new MediaRouteDynamicChooserDialog.MediaRouterCallback();
        mContext = context;
        mUpdateRoutesDelayMs = context.getResources().getInteger(
                R.integer.mr_update_routes_delay_ms);
    }

    /**
     * Gets the media route selector for filtering the routes that the user can select.
     *
     * @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.
     *
     * @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)) {
            mSelector = selector;

            if (mAttachedToWindow) {
                mRouter.removeCallback(mCallback);
                mRouter.addCallback(selector, mCallback,
                        MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
            }

            refreshRoutes();
        }
    }

    /**
     * Called to filter the set of routes that should be included in the list.
     * <p>
     * The default implementation iterates over all routes in the provided list and
     * removes those for which {@link #onFilterRoute} returns false.
     *
     * @param routes The list of routes to filter in-place, never null.
     */
    public void onFilterRoutes(@NonNull List<MediaRouter.RouteInfo> routes) {
        for (int i = routes.size(); i-- > 0; ) {
            if (!onFilterRoute(routes.get(i))) {
                routes.remove(i);
            }
        }
    }

    /**
     * Called to filter a specific route. Returns {@code true} to include in the picker dialog
     * <p>
     * The default implementation returns true for enabled non-default routes that
     * match the selector. Subclasses can override this method to filter routes
     * differently.
     *
     * @param route The route to consider, never null.
     * @return {@code true} if the route should be included in the device picker dialog.
     */
    public boolean onFilterRoute(@NonNull MediaRouter.RouteInfo route) {
        return !route.isDefaultOrBluetooth() && route.isEnabled()
                && route.matchesSelector(mSelector);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.mr_picker_dialog);
        MediaRouterThemeHelper.setDialogBackgroundColor(mContext, this);

        mRoutes = new ArrayList<>();
        mCloseButton = findViewById(R.id.mr_picker_close_button);
        mCloseButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });

        mAdapter = new RecyclerAdapter();
        mRecyclerView = findViewById(R.id.mr_picker_list);
        mRecyclerView.setAdapter(mAdapter);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));

        updateLayout();
    }

    /**
     * Sets the width of the dialog. Also called when configuration changes.
     */
    void updateLayout() {
        int width = MediaRouteDialogHelper.getDialogWidthForDynamicGroup(mContext);
        int height = MediaRouteDialogHelper.getDialogHeight(mContext);
        getWindow().setLayout(width, height);
    }

    @CallSuper
    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();

        mAttachedToWindow = true;
        mRouter.addCallback(mSelector, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
        refreshRoutes();
    }

    @CallSuper
    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        mAttachedToWindow = false;
        mRouter.removeCallback(mCallback);
        mHandler.removeMessages(MSG_UPDATE_ROUTES);
    }

    /**
     * Refreshes the list of routes that are shown in the device picker dialog.
     */
    public void refreshRoutes() {
        if (mSelectingRoute != null) {
            return;
        }

        if (mAttachedToWindow) {
            ArrayList<MediaRouter.RouteInfo> routes = new ArrayList<>(mRouter.getRoutes());
            onFilterRoutes(routes);
            Collections.sort(routes, MediaRouteDynamicChooserDialog.RouteComparator.sInstance);
            if (SystemClock.uptimeMillis() - mLastUpdateTime >= mUpdateRoutesDelayMs) {
                updateRoutes(routes);
            } else {
                mHandler.removeMessages(MSG_UPDATE_ROUTES);
                mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_UPDATE_ROUTES, routes),
                        mLastUpdateTime + mUpdateRoutesDelayMs);
            }
        }
    }

    void updateRoutes(List<MediaRouter.RouteInfo> routes) {
        mLastUpdateTime = SystemClock.uptimeMillis();
        mRoutes.clear();
        mRoutes.addAll(routes);
        mAdapter.rebuildItems();
    }

    private final class MediaRouterCallback extends MediaRouter.Callback {
        MediaRouterCallback() {
        }

        @Override
        public void onRouteAdded(@NonNull MediaRouter router, @NonNull MediaRouter.RouteInfo info) {
            refreshRoutes();
        }

        @Override
        public void onRouteRemoved(@NonNull MediaRouter router,
                @NonNull MediaRouter.RouteInfo info) {
            refreshRoutes();
        }

        @Override
        public void onRouteChanged(@NonNull MediaRouter router,
                @NonNull MediaRouter.RouteInfo info) {
            refreshRoutes();
        }

        @Override
        public void onRouteSelected(@NonNull MediaRouter router,
                @NonNull MediaRouter.RouteInfo route) {
            dismiss();
        }
    }

    static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> {
        public static final RouteComparator sInstance = new RouteComparator();

        @Override
        public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
            return lhs.getName().compareToIgnoreCase(rhs.getName());
        }
    }

    /**
     * This class stores a list of Item values that can contain information about both section
     * header(text of section header) and route(text of route name, icon of route type)
     */
    private final class RecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        private static final String TAG = "RecyclerAdapter";
        private final ArrayList<Item> mItems;

        private final LayoutInflater mInflater;
        private final Drawable mDefaultIcon;
        private final Drawable mTvIcon;
        private final Drawable mSpeakerIcon;
        private final Drawable mSpeakerGroupIcon;

        RecyclerAdapter() {
            mItems = new ArrayList<>();

            mInflater = LayoutInflater.from(mContext);
            mDefaultIcon = MediaRouterThemeHelper.getDefaultDrawableIcon(mContext);
            mTvIcon = MediaRouterThemeHelper.getTvDrawableIcon(mContext);
            mSpeakerIcon = MediaRouterThemeHelper.getSpeakerDrawableIcon(mContext);
            mSpeakerGroupIcon = MediaRouterThemeHelper.getSpeakerGroupDrawableIcon(mContext);
            rebuildItems();
        }

        // Create a list of items with mMemberRoutes and add them to mItems
        void rebuildItems() {
            mItems.clear();

            mItems.add(new Item(mContext.getString(R.string.mr_chooser_title)));
            for (MediaRouter.RouteInfo route : mRoutes) {
                mItems.add(new Item(route));
            }

            notifyDataSetChanged();
        }

        @Override
        @NonNull
        public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view;

            switch (viewType) {
                case ITEM_TYPE_HEADER:
                    view = mInflater.inflate(R.layout.mr_picker_header_item, parent, false);
                    return new HeaderViewHolder(view);
                case ITEM_TYPE_ROUTE:
                    view = mInflater.inflate(R.layout.mr_picker_route_item, parent, false);
                    return new RouteViewHolder(view);
                default:
                    // Never happens.
                    throw new IllegalStateException();
            }
        }

        @Override
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
            int viewType = getItemViewType(position);
            Item item = getItem(position);

            switch (viewType) {
                case ITEM_TYPE_HEADER:
                    ((HeaderViewHolder) holder).bindHeaderView(item);
                    break;
                case ITEM_TYPE_ROUTE:
                    ((RouteViewHolder) holder).bindRouteView(item);
                    break;
                default:
                    Log.w(TAG, "Cannot bind item to ViewHolder because of wrong view type");
            }
        }

        @Override
        public int getItemCount() {
            return mItems.size();
        }

        Drawable getIconDrawable(MediaRouter.RouteInfo route) {
            Uri iconUri = route.getIconUri();
            if (iconUri != null) {
                try {
                    InputStream is = mContext.getContentResolver().openInputStream(iconUri);
                    Drawable drawable = Drawable.createFromStream(is, null);
                    if (drawable != null) {
                        return drawable;
                    }
                } catch (IOException e) {
                    Log.w(TAG, "Failed to load " + iconUri, e);
                    // Falls back.
                }
            }
            return getDefaultIconDrawable(route);
        }

        private Drawable getDefaultIconDrawable(MediaRouter.RouteInfo route) {
            // If the type of the receiver device is specified, use it.
            switch (route.getDeviceType()) {
                case  MediaRouter.RouteInfo.DEVICE_TYPE_TV:
                    return mTvIcon;
                case MediaRouter.RouteInfo.DEVICE_TYPE_REMOTE_SPEAKER:
                    return mSpeakerIcon;
            }

            // Otherwise, make the best guess based on other route information.
            if (route.isGroup()) {
                // Only speakers can be grouped for now.
                return mSpeakerGroupIcon;
            }
            return mDefaultIcon;
        }

        @Override
        public int getItemViewType(int position) {
            return mItems.get(position).getType();
        }

        public Item getItem(int position) {
            return mItems.get(position);
        }

        /**
         * Item class contains information of section header(text of section header) and
         * route(text of route name, icon of route type)
         */
        private class Item {
            private final Object mData;
            private final int mType;

            Item(Object data) {
                mData = data;

                if (data instanceof String) {
                    mType = ITEM_TYPE_HEADER;
                } else if (data instanceof MediaRouter.RouteInfo) {
                    mType = ITEM_TYPE_ROUTE;
                } else {
                    // Never happens.
                    throw new IllegalArgumentException();
                }
            }

            public Object getData() {
                return mData;
            }

            public int getType() {
                return mType;
            }
        }

        // ViewHolder for section header list item
        private class HeaderViewHolder extends RecyclerView.ViewHolder {
            TextView mTextView;

            HeaderViewHolder(View itemView) {
                super(itemView);
                mTextView = itemView.findViewById(R.id.mr_picker_header_name);
            }

            public void bindHeaderView(Item item) {
                String headerName = item.getData().toString();

                mTextView.setText(headerName);
            }
        }

        // ViewHolder for route list item
        private class RouteViewHolder extends RecyclerView.ViewHolder {
            final View mItemView;
            final ImageView mImageView;
            final ProgressBar mProgressBar;
            final TextView mTextView;

            RouteViewHolder(View itemView) {
                super(itemView);
                mItemView = itemView;
                mImageView = itemView.findViewById(R.id.mr_picker_route_icon);
                mProgressBar = itemView.findViewById(R.id.mr_picker_route_progress_bar);
                mTextView = itemView.findViewById(R.id.mr_picker_route_name);

                MediaRouterThemeHelper.setIndeterminateProgressBarColor(mContext, mProgressBar);
            }

            public void bindRouteView(final Item item) {
                final MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) item.getData();
                mItemView.setVisibility(View.VISIBLE);
                mProgressBar.setVisibility(View.INVISIBLE);
                mItemView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        mSelectingRoute = route;
                        route.select();
                        mImageView.setVisibility(View.INVISIBLE);
                        mProgressBar.setVisibility(View.VISIBLE);
                    }
                });
                mTextView.setText(route.getName());
                mImageView.setImageDrawable(getIconDrawable(route));
            }
        }
    }
}