diff options
author | Jeff Brown <jeffbrown@google.com> | 2013-11-09 17:48:23 -0800 |
---|---|---|
committer | Jeff Brown <jeffbrown@google.com> | 2013-11-10 14:39:19 -0800 |
commit | 0abd3a6ce83ed23abe614155e776b600ef2a66c3 (patch) | |
tree | 0e1e4612ad24b020e63d494608defaf298ef8909 /core/java/com | |
parent | e2126baf72f007d079efcabd45c7a546db34361a (diff) | |
download | frameworks_base-0abd3a6ce83ed23abe614155e776b600ef2a66c3.zip frameworks_base-0abd3a6ce83ed23abe614155e776b600ef2a66c3.tar.gz frameworks_base-0abd3a6ce83ed23abe614155e776b600ef2a66c3.tar.bz2 |
Update the media router dialogs and integrate into system UI.
Port the new style UI back into the framework from the support library.
There are now two dialogs: a chooser and a controller. We use the
same dialogs for selecting routes within app and within quick settings.
Note that the new UI does not support any grouping features since they
are deprecated and unused.
Bug: 11257292
Change-Id: I64e936a18d25ab75f0c470cbc1e7085f67004863
Diffstat (limited to 'core/java/com')
6 files changed, 786 insertions, 703 deletions
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialog.java b/core/java/com/android/internal/app/MediaRouteChooserDialog.java new file mode 100644 index 0000000..b963c74 --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteChooserDialog.java @@ -0,0 +1,272 @@ +/* + * 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.Dialog; +import android.content.Context; +import android.media.MediaRouter; +import android.media.MediaRouter.RouteInfo; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.Comparator; +import java.util.List; + +/** + * This class implements the route chooser dialog for {@link MediaRouter}. + * <p> + * This dialog allows the user to choose a route that matches a given selector. + * </p> + * + * @see MediaRouteButton + * @see MediaRouteActionProvider + * + * TODO: Move this back into the API, as in the support library media router. + */ +public class MediaRouteChooserDialog extends Dialog { + private final MediaRouter mRouter; + private final MediaRouterCallback mCallback; + + private int mRouteTypes; + private View.OnClickListener mExtendedSettingsClickListener; + private RouteAdapter mAdapter; + private ListView mListView; + private Button mExtendedSettingsButton; + private boolean mAttachedToWindow; + + public MediaRouteChooserDialog(Context context, int theme) { + super(context, theme); + + mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); + mCallback = new MediaRouterCallback(); + } + + /** + * 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) { + mRouteTypes = types; + + if (mAttachedToWindow) { + mRouter.removeCallback(mCallback); + mRouter.addCallback(types, mCallback, + MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); + } + + refreshRoutes(); + } + } + + public void setExtendedSettingsClickListener(View.OnClickListener listener) { + if (listener != mExtendedSettingsClickListener) { + mExtendedSettingsClickListener = listener; + updateExtendedSettingsButton(); + } + } + + /** + * Returns true if the route should be included in the list. + * <p> + * The default implementation returns true for non-default routes that + * match the selector. Subclasses can override this method to filter routes + * differently. + * </p> + * + * @param route The route to consider, never null. + * @return True if the route should be included in the chooser dialog. + */ + public boolean onFilterRoute(MediaRouter.RouteInfo route) { + return !route.isDefault() && route.matchesTypes(mRouteTypes); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getWindow().requestFeature(Window.FEATURE_LEFT_ICON); + + setContentView(R.layout.media_route_chooser_dialog); + setTitle(R.string.media_route_chooser_title); + + // Must be called after setContentView. + getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, + R.drawable.ic_media_route_off_holo_dark); + + mAdapter = new RouteAdapter(getContext()); + mListView = (ListView)findViewById(R.id.media_route_list); + mListView.setAdapter(mAdapter); + mListView.setOnItemClickListener(mAdapter); + mListView.setEmptyView(findViewById(android.R.id.empty)); + + mExtendedSettingsButton = (Button)findViewById(R.id.media_route_extended_settings_button); + updateExtendedSettingsButton(); + } + + private void updateExtendedSettingsButton() { + if (mExtendedSettingsButton != null) { + mExtendedSettingsButton.setOnClickListener(mExtendedSettingsClickListener); + mExtendedSettingsButton.setVisibility( + mExtendedSettingsClickListener != null ? View.VISIBLE : View.GONE); + } + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + mAttachedToWindow = true; + mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); + refreshRoutes(); + } + + @Override + public void onDetachedFromWindow() { + mAttachedToWindow = false; + mRouter.removeCallback(mCallback); + + super.onDetachedFromWindow(); + } + + /** + * Refreshes the list of routes that are shown in the chooser dialog. + */ + public void refreshRoutes() { + if (mAttachedToWindow) { + mAdapter.update(); + } + } + + private final class RouteAdapter extends ArrayAdapter<MediaRouter.RouteInfo> + implements ListView.OnItemClickListener { + private final LayoutInflater mInflater; + + public RouteAdapter(Context context) { + super(context, 0); + mInflater = LayoutInflater.from(context); + } + + public void update() { + clear(); + final int count = mRouter.getRouteCount(); + for (int i = 0; i < count; i++) { + MediaRouter.RouteInfo route = mRouter.getRouteAt(i); + if (onFilterRoute(route)) { + add(route); + } + } + sort(RouteComparator.sInstance); + notifyDataSetChanged(); + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int position) { + return getItem(position).isEnabled(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view = convertView; + if (view == null) { + view = mInflater.inflate(R.layout.media_route_list_item, parent, false); + } + MediaRouter.RouteInfo route = getItem(position); + TextView text1 = (TextView)view.findViewById(android.R.id.text1); + TextView text2 = (TextView)view.findViewById(android.R.id.text2); + text1.setText(route.getName()); + CharSequence description = route.getDescription(); + if (TextUtils.isEmpty(description)) { + text2.setVisibility(View.GONE); + text2.setText(""); + } else { + text2.setVisibility(View.VISIBLE); + text2.setText(description); + } + view.setEnabled(route.isEnabled()); + return view; + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + MediaRouter.RouteInfo route = getItem(position); + if (route.isEnabled()) { + route.select(); + dismiss(); + } + } + } + + private final class MediaRouterCallback extends MediaRouter.SimpleCallback { + @Override + public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) { + refreshRoutes(); + } + + @Override + public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) { + refreshRoutes(); + } + + @Override + public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) { + refreshRoutes(); + } + + @Override + public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { + dismiss(); + } + } + + private 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().toString().compareTo(rhs.getName().toString()); + } + } +} diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java index 268dcf6..ae362af 100644 --- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java +++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * 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. @@ -16,675 +16,86 @@ package com.android.internal.app; -import com.android.internal.R; - -import android.app.Activity; import android.app.Dialog; import android.app.DialogFragment; -import android.app.MediaRouteActionProvider; -import android.app.MediaRouteButton; import android.content.Context; -import android.graphics.drawable.Drawable; -import android.hardware.display.DisplayManager; -import android.media.MediaRouter; -import android.media.MediaRouter.RouteCategory; -import android.media.MediaRouter.RouteGroup; -import android.media.MediaRouter.RouteInfo; import android.os.Bundle; -import android.text.TextUtils; -import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.CheckBox; -import android.widget.Checkable; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.SeekBar; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import android.view.View.OnClickListener; /** - * This class implements the route chooser dialog for {@link MediaRouter}. + * Media route chooser dialog fragment. + * <p> + * Creates a {@link MediaRouteChooserDialog}. The application may subclass + * this dialog fragment to customize the media route chooser dialog. + * </p> * - * @see MediaRouteButton - * @see MediaRouteActionProvider + * TODO: Move this back into the API, as in the support library media router. */ public class MediaRouteChooserDialogFragment extends DialogFragment { - private static final String TAG = "MediaRouteChooserDialogFragment"; - public static final String FRAGMENT_TAG = "android:MediaRouteChooserDialogFragment"; - - private static final int[] ITEM_LAYOUTS = new int[] { - R.layout.media_route_list_item_top_header, - R.layout.media_route_list_item_section_header, - R.layout.media_route_list_item, - R.layout.media_route_list_item_checkable, - R.layout.media_route_list_item_collapse_group - }; + private final String ARGUMENT_ROUTE_TYPES = "routeTypes"; - MediaRouter mRouter; - private int mRouteTypes; - - private LayoutInflater mInflater; - private LauncherListener mLauncherListener; - private View.OnClickListener mExtendedSettingsListener; - private RouteAdapter mAdapter; - private ListView mListView; - private SeekBar mVolumeSlider; - private ImageView mVolumeIcon; - - final RouteComparator mComparator = new RouteComparator(); - final MediaRouterCallback mCallback = new MediaRouterCallback(); - private boolean mIgnoreSliderVolumeChanges; - private boolean mIgnoreCallbackVolumeChanges; + private View.OnClickListener mExtendedSettingsClickListener; + /** + * Creates a media route chooser dialog fragment. + * <p> + * All subclasses of this class must also possess a default constructor. + * </p> + */ public MediaRouteChooserDialogFragment() { - setStyle(STYLE_NO_TITLE, R.style.Theme_DeviceDefault_Dialog); + setCancelable(true); + setStyle(STYLE_NORMAL, android.R.style.Theme_DeviceDefault_Dialog); } - public void setLauncherListener(LauncherListener listener) { - mLauncherListener = listener; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mRouter = (MediaRouter) activity.getSystemService(Context.MEDIA_ROUTER_SERVICE); - mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); - } - - @Override - public void onDetach() { - super.onDetach(); - if (mLauncherListener != null) { - mLauncherListener.onDetached(this); - } - if (mAdapter != null) { - mAdapter = null; - } - mInflater = null; - mRouter.removeCallback(mCallback); - mRouter = null; - } - - public void setExtendedSettingsClickListener(View.OnClickListener listener) { - mExtendedSettingsListener = listener; + public int getRouteTypes() { + Bundle args = getArguments(); + return args != null ? args.getInt(ARGUMENT_ROUTE_TYPES) : 0; } public void setRouteTypes(int types) { - mRouteTypes = types; - } - - void updateVolume() { - if (mRouter == null) return; - - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - mVolumeIcon.setImageResource(selectedRoute == null || - selectedRoute.getPlaybackType() == RouteInfo.PLAYBACK_TYPE_LOCAL ? - R.drawable.ic_audio_vol : R.drawable.ic_media_route_on_holo_dark); - - mIgnoreSliderVolumeChanges = true; - - if (selectedRoute == null || - selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_FIXED) { - // Disable the slider and show it at max volume. - mVolumeSlider.setMax(1); - mVolumeSlider.setProgress(1); - mVolumeSlider.setEnabled(false); - } else { - mVolumeSlider.setEnabled(true); - mVolumeSlider.setMax(selectedRoute.getVolumeMax()); - mVolumeSlider.setProgress(selectedRoute.getVolume()); - } - - mIgnoreSliderVolumeChanges = false; - } - - void changeVolume(int newValue) { - if (mIgnoreSliderVolumeChanges) return; - - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - if (selectedRoute != null && - selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_VARIABLE) { - final int maxVolume = selectedRoute.getVolumeMax(); - newValue = Math.max(0, Math.min(newValue, maxVolume)); - selectedRoute.requestSetVolume(newValue); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mInflater = inflater; - final View layout = inflater.inflate(R.layout.media_route_chooser_layout, container, false); - - mVolumeIcon = (ImageView) layout.findViewById(R.id.volume_icon); - mVolumeSlider = (SeekBar) layout.findViewById(R.id.volume_slider); - updateVolume(); - mVolumeSlider.setOnSeekBarChangeListener(new VolumeSliderChangeListener()); - - if (mExtendedSettingsListener != null) { - final View extendedSettingsButton = layout.findViewById(R.id.extended_settings); - extendedSettingsButton.setVisibility(View.VISIBLE); - extendedSettingsButton.setOnClickListener(mExtendedSettingsListener); - } - - final ListView list = (ListView) layout.findViewById(R.id.list); - list.setItemsCanFocus(true); - list.setAdapter(mAdapter = new RouteAdapter()); - list.setOnItemClickListener(mAdapter); - - mListView = list; - - mAdapter.scrollToSelectedItem(); - - return layout; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - return new RouteChooserDialog(getActivity(), getTheme()); - } - - private static class ViewHolder { - public TextView text1; - public TextView text2; - public ImageView icon; - public ImageButton expandGroupButton; - public RouteAdapter.ExpandGroupListener expandGroupListener; - public int position; - public CheckBox check; - } - - private class RouteAdapter extends BaseAdapter implements ListView.OnItemClickListener { - private static final int VIEW_TOP_HEADER = 0; - private static final int VIEW_SECTION_HEADER = 1; - private static final int VIEW_ROUTE = 2; - private static final int VIEW_GROUPING_ROUTE = 3; - private static final int VIEW_GROUPING_DONE = 4; - - private int mSelectedItemPosition = -1; - private final ArrayList<Object> mItems = new ArrayList<Object>(); - - private RouteCategory mCategoryEditingGroups; - private RouteGroup mEditingGroup; - - // Temporary lists for manipulation - private final ArrayList<RouteInfo> mCatRouteList = new ArrayList<RouteInfo>(); - private final ArrayList<RouteInfo> mSortRouteList = new ArrayList<RouteInfo>(); - - private boolean mIgnoreUpdates; - - RouteAdapter() { - update(); - } - - void update() { - /* - * This is kind of wacky, but our data sets are going to be - * fairly small on average. Ideally we should be able to do some of this stuff - * in-place instead. - * - * Basic idea: each entry in mItems represents an item in the list for quick access. - * Entries can be a RouteCategory (section header), a RouteInfo with a category of - * mCategoryEditingGroups (a flattened RouteInfo pulled out of its group, allowing - * the user to change the group), - */ - if (mIgnoreUpdates) return; - - mItems.clear(); - - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - mSelectedItemPosition = -1; - - List<RouteInfo> routes; - final int catCount = mRouter.getCategoryCount(); - for (int i = 0; i < catCount; i++) { - final RouteCategory cat = mRouter.getCategoryAt(i); - routes = cat.getRoutes(mCatRouteList); - - if (!cat.isSystem()) { - mItems.add(cat); - } - - if (cat == mCategoryEditingGroups) { - addGroupEditingCategoryRoutes(routes); - } else { - addSelectableRoutes(selectedRoute, routes); - } - - routes.clear(); + if (types != getRouteTypes()) { + Bundle args = getArguments(); + if (args == null) { + args = new Bundle(); } + args.putInt(ARGUMENT_ROUTE_TYPES, types); + setArguments(args); - notifyDataSetChanged(); - if (mListView != null && mSelectedItemPosition >= 0) { - mListView.setItemChecked(mSelectedItemPosition, true); - } - } - - void scrollToEditingGroup() { - if (mCategoryEditingGroups == null || mListView == null) return; - - int pos = 0; - int bound = 0; - final int itemCount = mItems.size(); - for (int i = 0; i < itemCount; i++) { - final Object item = mItems.get(i); - if (item != null && item == mCategoryEditingGroups) { - bound = i; - } - if (item == null) { - pos = i; - break; // this is always below the category header; we can stop here. - } - } - - mListView.smoothScrollToPosition(pos, bound); - } - - void scrollToSelectedItem() { - if (mListView == null || mSelectedItemPosition < 0) return; - - mListView.smoothScrollToPosition(mSelectedItemPosition); - } - - void addSelectableRoutes(RouteInfo selectedRoute, List<RouteInfo> from) { - final int routeCount = from.size(); - for (int j = 0; j < routeCount; j++) { - final RouteInfo info = from.get(j); - if (info == selectedRoute) { - mSelectedItemPosition = mItems.size(); - } - mItems.add(info); - } - } - - void addGroupEditingCategoryRoutes(List<RouteInfo> from) { - // Unpack groups and flatten for presentation - // mSortRouteList will always be empty here. - final int topCount = from.size(); - for (int i = 0; i < topCount; i++) { - final RouteInfo route = from.get(i); - final RouteGroup group = route.getGroup(); - if (group == route) { - // This is a group, unpack it. - final int groupCount = group.getRouteCount(); - for (int j = 0; j < groupCount; j++) { - final RouteInfo innerRoute = group.getRouteAt(j); - mSortRouteList.add(innerRoute); - } - } else { - mSortRouteList.add(route); - } - } - // Sort by name. This will keep the route positions relatively stable even though they - // will be repeatedly added and removed. - Collections.sort(mSortRouteList, mComparator); - - mItems.addAll(mSortRouteList); - mSortRouteList.clear(); - - mItems.add(null); // Sentinel reserving space for the "done" button. - } - - @Override - public int getCount() { - return mItems.size(); - } - - @Override - public int getViewTypeCount() { - return 5; - } - - @Override - public int getItemViewType(int position) { - final Object item = getItem(position); - if (item instanceof RouteCategory) { - return position == 0 ? VIEW_TOP_HEADER : VIEW_SECTION_HEADER; - } else if (item == null) { - return VIEW_GROUPING_DONE; - } else { - final RouteInfo info = (RouteInfo) item; - if (info.getCategory() == mCategoryEditingGroups) { - return VIEW_GROUPING_ROUTE; - } - return VIEW_ROUTE; - } - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int position) { - switch (getItemViewType(position)) { - case VIEW_ROUTE: - return ((RouteInfo) mItems.get(position)).isEnabled(); - case VIEW_GROUPING_ROUTE: - case VIEW_GROUPING_DONE: - return true; - default: - return false; - } - } - - @Override - public Object getItem(int position) { - return mItems.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final int viewType = getItemViewType(position); - - ViewHolder holder; - if (convertView == null) { - convertView = mInflater.inflate(ITEM_LAYOUTS[viewType], parent, false); - holder = new ViewHolder(); - holder.position = position; - holder.text1 = (TextView) convertView.findViewById(R.id.text1); - holder.text2 = (TextView) convertView.findViewById(R.id.text2); - holder.icon = (ImageView) convertView.findViewById(R.id.icon); - holder.check = (CheckBox) convertView.findViewById(R.id.check); - holder.expandGroupButton = (ImageButton) convertView.findViewById( - R.id.expand_button); - if (holder.expandGroupButton != null) { - holder.expandGroupListener = new ExpandGroupListener(); - holder.expandGroupButton.setOnClickListener(holder.expandGroupListener); - } - - final View fview = convertView; - final ListView list = (ListView) parent; - final ViewHolder fholder = holder; - convertView.setOnClickListener(new View.OnClickListener() { - @Override public void onClick(View v) { - list.performItemClick(fview, fholder.position, 0); - } - }); - convertView.setTag(holder); - } else { - holder = (ViewHolder) convertView.getTag(); - holder.position = position; - } - - switch (viewType) { - case VIEW_ROUTE: - case VIEW_GROUPING_ROUTE: - bindItemView(position, holder); - break; - case VIEW_SECTION_HEADER: - case VIEW_TOP_HEADER: - bindHeaderView(position, holder); - break; - } - - convertView.setActivated(position == mSelectedItemPosition); - convertView.setEnabled(isEnabled(position)); - - return convertView; - } - - void bindItemView(int position, ViewHolder holder) { - RouteInfo info = (RouteInfo) mItems.get(position); - holder.text1.setText(info.getName(getActivity())); - final CharSequence status = info.getStatus(); - if (TextUtils.isEmpty(status)) { - holder.text2.setVisibility(View.GONE); - } else { - holder.text2.setVisibility(View.VISIBLE); - holder.text2.setText(status); - } - Drawable icon = info.getIconDrawable(); - if (icon != null) { - // Make sure we have a fresh drawable where it doesn't matter if we mutate it - icon = icon.getConstantState().newDrawable(getResources()); - } - holder.icon.setImageDrawable(icon); - holder.icon.setVisibility(icon != null ? View.VISIBLE : View.GONE); - - RouteCategory cat = info.getCategory(); - boolean canGroup = false; - if (cat == mCategoryEditingGroups) { - RouteGroup group = info.getGroup(); - holder.check.setEnabled(group.getRouteCount() > 1); - holder.check.setChecked(group == mEditingGroup); - } else { - if (cat.isGroupable()) { - final RouteGroup group = (RouteGroup) info; - canGroup = group.getRouteCount() > 1 || - getItemViewType(position - 1) == VIEW_ROUTE || - (position < getCount() - 1 && - getItemViewType(position + 1) == VIEW_ROUTE); - } - } - - if (holder.expandGroupButton != null) { - holder.expandGroupButton.setVisibility(canGroup ? View.VISIBLE : View.GONE); - holder.expandGroupListener.position = position; - } - } - - void bindHeaderView(int position, ViewHolder holder) { - RouteCategory cat = (RouteCategory) mItems.get(position); - holder.text1.setText(cat.getName(getActivity())); - } - - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - final int type = getItemViewType(position); - if (type == VIEW_SECTION_HEADER || type == VIEW_TOP_HEADER) { - return; - } else if (type == VIEW_GROUPING_DONE) { - finishGrouping(); - return; - } else { - final Object item = getItem(position); - if (!(item instanceof RouteInfo)) { - // Oops. Stale event running around? Skip it. - return; - } - - final RouteInfo route = (RouteInfo) item; - if (type == VIEW_ROUTE) { - mRouter.selectRouteInt(mRouteTypes, route, true); - dismiss(); - } else if (type == VIEW_GROUPING_ROUTE) { - final Checkable c = (Checkable) view; - final boolean wasChecked = c.isChecked(); - - mIgnoreUpdates = true; - RouteGroup oldGroup = route.getGroup(); - if (!wasChecked && oldGroup != mEditingGroup) { - // Assumption: in a groupable category oldGroup will never be null. - if (mRouter.getSelectedRoute(mRouteTypes) == oldGroup) { - // Old group was selected but is now empty. Select the group - // we're manipulating since that's where the last route went. - mRouter.selectRouteInt(mRouteTypes, mEditingGroup, true); - } - oldGroup.removeRoute(route); - mEditingGroup.addRoute(route); - c.setChecked(true); - } else if (wasChecked && mEditingGroup.getRouteCount() > 1) { - mEditingGroup.removeRoute(route); - - // In a groupable category this will add - // the route into its own new group. - mRouter.addRouteInt(route); - } - mIgnoreUpdates = false; - update(); - } - } - } - - boolean isGrouping() { - return mCategoryEditingGroups != null; - } - - void finishGrouping() { - mCategoryEditingGroups = null; - mEditingGroup = null; - getDialog().setCanceledOnTouchOutside(true); - update(); - scrollToSelectedItem(); - } - - class ExpandGroupListener implements View.OnClickListener { - int position; - - @Override - public void onClick(View v) { - // Assumption: this is only available for the user to click if we're presenting - // a groupable category, where every top-level route in the category is a group. - final RouteGroup group = (RouteGroup) getItem(position); - mEditingGroup = group; - mCategoryEditingGroups = group.getCategory(); - getDialog().setCanceledOnTouchOutside(false); - mRouter.selectRouteInt(mRouteTypes, mEditingGroup, true); - update(); - scrollToEditingGroup(); - } - } - } - - class MediaRouterCallback extends MediaRouter.Callback { - @Override - public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { - mAdapter.update(); - updateVolume(); - } - - @Override - public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { - mAdapter.update(); - } - - @Override - public void onRouteAdded(MediaRouter router, RouteInfo info) { - mAdapter.update(); - } - - @Override - public void onRouteRemoved(MediaRouter router, RouteInfo info) { - if (info == mAdapter.mEditingGroup) { - mAdapter.finishGrouping(); - } - mAdapter.update(); - } - - @Override - public void onRouteChanged(MediaRouter router, RouteInfo info) { - mAdapter.notifyDataSetChanged(); - } - - @Override - public void onRouteGrouped(MediaRouter router, RouteInfo info, - RouteGroup group, int index) { - mAdapter.update(); - } - - @Override - public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { - mAdapter.update(); - } - - @Override - public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) { - if (!mIgnoreCallbackVolumeChanges) { - updateVolume(); + MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog(); + if (dialog != null) { + dialog.setRouteTypes(types); } } } - class RouteComparator implements Comparator<RouteInfo> { - @Override - public int compare(RouteInfo lhs, RouteInfo rhs) { - return lhs.getName(getActivity()).toString() - .compareTo(rhs.getName(getActivity()).toString()); - } - } - - class RouteChooserDialog extends Dialog { - public RouteChooserDialog(Context context, int theme) { - super(context, theme); - } - - @Override - public void onBackPressed() { - if (mAdapter != null && mAdapter.isGrouping()) { - mAdapter.finishGrouping(); - } else { - super.onBackPressed(); - } - } - - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) { - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - if (selectedRoute != null) { - selectedRoute.requestUpdateVolume(-1); - return true; - } - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) { - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - if (selectedRoute != null) { - mRouter.getSelectedRoute(mRouteTypes).requestUpdateVolume(1); - return true; - } - } - return super.onKeyDown(keyCode, event); - } + public void setExtendedSettingsClickListener(View.OnClickListener listener) { + if (listener != mExtendedSettingsClickListener) { + mExtendedSettingsClickListener = listener; - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) { - return true; - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) { - return true; - } else { - return super.onKeyUp(keyCode, event); + MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog(); + if (dialog != null) { + dialog.setExtendedSettingsClickListener(listener); } } } /** - * Implemented by the MediaRouteButton that launched this dialog + * Called when the chooser dialog is being created. + * <p> + * Subclasses may override this method to customize the dialog. + * </p> */ - public interface LauncherListener { - public void onDetached(MediaRouteChooserDialogFragment detachedFragment); + public MediaRouteChooserDialog onCreateChooserDialog( + Context context, Bundle savedInstanceState) { + return new MediaRouteChooserDialog(context, getTheme()); } - class VolumeSliderChangeListener implements SeekBar.OnSeekBarChangeListener { - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - changeVolume(progress); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - mIgnoreCallbackVolumeChanges = true; - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - mIgnoreCallbackVolumeChanges = false; - updateVolume(); - } - + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + MediaRouteChooserDialog dialog = onCreateChooserDialog(getActivity(), savedInstanceState); + dialog.setRouteTypes(getRouteTypes()); + dialog.setExtendedSettingsClickListener(mExtendedSettingsClickListener); + return dialog; } } diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java new file mode 100644 index 0000000..8fc99c7 --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java @@ -0,0 +1,318 @@ +/* + * 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.Dialog; +import android.app.MediaRouteActionProvider; +import android.app.MediaRouteButton; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.media.MediaRouter; +import android.media.MediaRouter.RouteGroup; +import android.media.MediaRouter.RouteInfo; +import android.os.Bundle; +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. + * </p> + * + * @see MediaRouteButton + * @see MediaRouteActionProvider + * + * TODO: Move this back into the API, as in the support library media router. + */ +public class MediaRouteControllerDialog extends Dialog { + // 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 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; + + 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. + * <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.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() { + 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); + } + } + }); + + mDisconnectButton = (Button)findViewById(R.id.media_route_disconnect_button); + mDisconnectButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mRoute.isSelected()) { + mRouter.getDefaultRoute().select(); + } + 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(0, 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 (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; + getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, icon); + } + return true; + } + + private Drawable getIconDrawable() { + if (mRoute.isConnecting()) { + if (mMediaRouteConnectingDrawable == null) { + mMediaRouteConnectingDrawable = getContext().getResources().getDrawable( + R.drawable.ic_media_route_connecting_holo_dark); + } + return mMediaRouteConnectingDrawable; + } else { + if (mMediaRouteOnDrawable == null) { + mMediaRouteOnDrawable = getContext().getResources().getDrawable( + R.drawable.ic_media_route_on_holo_dark); + } + 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.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(); + } + } +} diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java b/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java new file mode 100644 index 0000000..108e81f --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java @@ -0,0 +1,60 @@ +/* + * 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 android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.os.Bundle; + +/** + * Media route controller dialog fragment. + * <p> + * Creates a {@link MediaRouteControllerDialog}. The application may subclass + * this dialog fragment to customize the media route controller dialog. + * </p> + * + * TODO: Move this back into the API, as in the support library media router. + */ +public class MediaRouteControllerDialogFragment extends DialogFragment { + /** + * Creates a media route controller dialog fragment. + * <p> + * All subclasses of this class must also possess a default constructor. + * </p> + */ + public MediaRouteControllerDialogFragment() { + setCancelable(true); + setStyle(STYLE_NORMAL, android.R.style.Theme_DeviceDefault_Dialog); + } + + /** + * Called when the controller dialog is being created. + * <p> + * Subclasses may override this method to customize the dialog. + * </p> + */ + public MediaRouteControllerDialog onCreateControllerDialog( + Context context, Bundle savedInstanceState) { + return new MediaRouteControllerDialog(context, getTheme()); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return onCreateControllerDialog(getActivity(), savedInstanceState); + } +} diff --git a/core/java/com/android/internal/app/MediaRouteDialogPresenter.java b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java new file mode 100644 index 0000000..fad7fd4 --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java @@ -0,0 +1,87 @@ +/* + * 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 android.app.Activity; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentManager; +import android.content.Context; +import android.media.MediaRouter; +import android.util.Log; +import android.view.View; + +/** + * Shows media route dialog as appropriate. + * @hide + */ +public abstract class MediaRouteDialogPresenter { + private static final String TAG = "MediaRouter"; + + private static final String CHOOSER_FRAGMENT_TAG = + "android.app.MediaRouteButton:MediaRouteChooserDialogFragment"; + private static final String CONTROLLER_FRAGMENT_TAG = + "android.app.MediaRouteButton:MediaRouteControllerDialogFragment"; + + public static DialogFragment showDialogFragment(Activity activity, + int routeTypes, View.OnClickListener extendedSettingsClickListener) { + final MediaRouter router = (MediaRouter)activity.getSystemService( + Context.MEDIA_ROUTER_SERVICE); + final FragmentManager fm = activity.getFragmentManager(); + + MediaRouter.RouteInfo route = router.getSelectedRoute(); + if (route.isDefault() || !route.matchesTypes(routeTypes)) { + if (fm.findFragmentByTag(CHOOSER_FRAGMENT_TAG) != null) { + Log.w(TAG, "showDialog(): Route chooser dialog already showing!"); + return null; + } + MediaRouteChooserDialogFragment f = new MediaRouteChooserDialogFragment(); + f.setRouteTypes(routeTypes); + f.setExtendedSettingsClickListener(extendedSettingsClickListener); + f.show(fm, CHOOSER_FRAGMENT_TAG); + return f; + } else { + if (fm.findFragmentByTag(CONTROLLER_FRAGMENT_TAG) != null) { + Log.w(TAG, "showDialog(): Route controller dialog already showing!"); + return null; + } + MediaRouteControllerDialogFragment f = new MediaRouteControllerDialogFragment(); + f.show(fm, CONTROLLER_FRAGMENT_TAG); + return f; + } + } + + public static Dialog createDialog(Context context, + int routeTypes, View.OnClickListener extendedSettingsClickListener) { + final MediaRouter router = (MediaRouter)context.getSystemService( + Context.MEDIA_ROUTER_SERVICE); + + MediaRouter.RouteInfo route = router.getSelectedRoute(); + if (route.isDefault() || !route.matchesTypes(routeTypes)) { + final MediaRouteChooserDialog d = new MediaRouteChooserDialog( + context, android.R.style.Theme_DeviceDefault_Dialog); + d.setRouteTypes(routeTypes); + d.setExtendedSettingsClickListener(extendedSettingsClickListener); + return d; + } else { + MediaRouteControllerDialog d = new MediaRouteControllerDialog( + context, android.R.style.Theme_DeviceDefault_Dialog); + return d; + } + } +} diff --git a/core/java/com/android/internal/view/CheckableLinearLayout.java b/core/java/com/android/internal/view/CheckableLinearLayout.java deleted file mode 100644 index 3fb7cec..0000000 --- a/core/java/com/android/internal/view/CheckableLinearLayout.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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 com.android.internal.view; - -import com.android.internal.R; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.Checkable; -import android.widget.CheckBox; -import android.widget.LinearLayout; - -public class CheckableLinearLayout extends LinearLayout implements Checkable { - private CheckBox mCheckBox; - - public CheckableLinearLayout(Context context) { - super(context); - // TODO Auto-generated constructor stub - } - - public CheckableLinearLayout(Context context, AttributeSet attrs) { - super(context, attrs); - // TODO Auto-generated constructor stub - } - - public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - // TODO Auto-generated constructor stub - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mCheckBox = (CheckBox) findViewById(R.id.check); - } - - @Override - public void setChecked(boolean checked) { - mCheckBox.setChecked(checked); - } - - @Override - public boolean isChecked() { - return mCheckBox.isChecked(); - } - - @Override - public void toggle() { - mCheckBox.toggle(); - } -} |