Merge "Support route grouping in the MediaRouter dialog UI." into jb-dev

This commit is contained in:
Adam Powell
2012-06-14 11:25:20 -07:00
committed by Android (Google) Code Review
17 changed files with 689 additions and 49 deletions

View File

@ -19,10 +19,12 @@ 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.media.MediaRouter;
import android.media.MediaRouter.RouteCategory;
import android.media.MediaRouter.RouteGroup;
@ -34,10 +36,14 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
/**
* This class implements the route chooser dialog for {@link MediaRouter}.
@ -49,14 +55,30 @@ 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
};
private static final int[] GROUP_ITEM_LAYOUTS = new int[] {
R.layout.media_route_list_item_top_header,
R.layout.media_route_list_item_checkable,
R.layout.media_route_list_item_collapse_group
};
MediaRouter mRouter;
private int mRouteTypes;
private LayoutInflater mInflater;
private LauncherListener mLauncherListener;
private View.OnClickListener mExtendedSettingsListener;
private RouteAdapter mAdapter;
private GroupAdapter mGroupAdapter;
private ListView mListView;
static final RouteComparator sComparator = new RouteComparator();
public MediaRouteChooserDialogFragment() {
setStyle(STYLE_NO_TITLE, R.style.Theme_DeviceDefault_Dialog);
}
@ -77,10 +99,15 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
if (mLauncherListener != null) {
mLauncherListener.onDetached(this);
}
if (mGroupAdapter != null) {
mRouter.removeCallback(mGroupAdapter);
mGroupAdapter = null;
}
if (mAdapter != null) {
mRouter.removeCallback(mAdapter);
mAdapter = null;
}
mInflater = null;
mRouter = null;
}
@ -102,6 +129,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
@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);
final View extendedSettingsButton = layout.findViewById(R.id.extended_settings);
@ -112,7 +140,8 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
final ListView list = (ListView) layout.findViewById(R.id.list);
list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
list.setAdapter(mAdapter = new RouteAdapter(inflater));
list.setItemsCanFocus(true);
list.setAdapter(mAdapter = new RouteAdapter());
list.setItemChecked(mAdapter.getSelectedRoutePosition(), true);
list.setOnItemClickListener(mAdapter);
@ -122,11 +151,59 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
return layout;
}
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
};
void onExpandGroup(RouteGroup info) {
mGroupAdapter = new GroupAdapter(info);
mRouter.addCallback(mRouteTypes, mGroupAdapter);
mListView.setAdapter(mGroupAdapter);
mListView.setOnItemClickListener(mGroupAdapter);
mListView.setItemsCanFocus(false);
mListView.clearChoices();
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
mGroupAdapter.initCheckedItems();
getDialog().setCanceledOnTouchOutside(false);
}
void onDoneGrouping() {
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(mAdapter);
mListView.setItemsCanFocus(true);
mListView.clearChoices();
mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
mListView.setItemChecked(mAdapter.getSelectedRoutePosition(), true);
mRouter.removeCallback(mGroupAdapter);
mGroupAdapter = null;
getDialog().setCanceledOnTouchOutside(true);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new RouteChooserDialog(getActivity(), getTheme());
}
@Override
public void onResume() {
super.onResume();
if (mListView != null) {
if (mGroupAdapter != null) {
mGroupAdapter.initCheckedItems();
} else {
mListView.setItemChecked(mAdapter.getSelectedRoutePosition(), true);
}
}
}
private static class ViewHolder {
public TextView text1;
public TextView text2;
public ImageView icon;
public ImageButton expandGroupButton;
public RouteAdapter.ExpandGroupListener expandGroupListener;
public int position;
}
private class RouteAdapter extends BaseAdapter implements MediaRouter.Callback,
ListView.OnItemClickListener {
@ -136,10 +213,8 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
private int mSelectedItemPosition;
private final ArrayList<Object> mItems = new ArrayList<Object>();
private final LayoutInflater mInflater;
RouteAdapter(LayoutInflater inflater) {
mInflater = inflater;
RouteAdapter() {
update();
}
@ -222,11 +297,29 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
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.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;
}
if (viewType == VIEW_ROUTE) {
@ -248,6 +341,24 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
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.isGroupable()) {
final RouteGroup group = (RouteGroup) info;
canGroup = group.getRouteCount() > 1 ||
getItemViewType(position - 1) == VIEW_ROUTE ||
(position < getCount() - 1 && getItemViewType(position + 1) == VIEW_ROUTE);
}
holder.expandGroupButton.setVisibility(canGroup ? View.VISIBLE : View.GONE);
holder.expandGroupListener.position = position;
}
void bindHeaderView(int position, ViewHolder holder) {
@ -306,36 +417,274 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
mRouter.selectRoute(mRouteTypes, (RouteInfo) item);
dismiss();
}
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.
onExpandGroup((RouteGroup) getItem(position));
}
}
}
private static class ViewHolder {
public TextView text1;
public TextView text2;
}
private class GroupAdapter extends BaseAdapter implements MediaRouter.Callback,
ListView.OnItemClickListener {
private static final int VIEW_HEADER = 0;
private static final int VIEW_ROUTE = 1;
private static final int VIEW_DONE = 2;
private RouteGroup mPrimary;
private RouteCategory mCategory;
private final ArrayList<RouteInfo> mTempList = new ArrayList<RouteInfo>();
private final ArrayList<RouteInfo> mFlatRoutes = new ArrayList<RouteInfo>();
private boolean mIgnoreUpdates;
public GroupAdapter(RouteGroup primary) {
mPrimary = primary;
mCategory = primary.getCategory();
update();
}
private class GroupAdapter extends BaseAdapter {
@Override
public int getCount() {
// TODO Auto-generated method stub
return 0;
return mFlatRoutes.size() + 2;
}
@Override
public int getViewTypeCount() {
return 3;
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return VIEW_HEADER;
} else if (position == getCount() - 1) {
return VIEW_DONE;
}
return VIEW_ROUTE;
}
void update() {
if (mIgnoreUpdates) return;
mFlatRoutes.clear();
mCategory.getRoutes(mTempList);
// Unpack groups and flatten for presentation
final int topCount = mTempList.size();
for (int i = 0; i < topCount; i++) {
final RouteInfo route = mTempList.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);
mFlatRoutes.add(innerRoute);
}
} else {
mFlatRoutes.add(route);
}
}
mTempList.clear();
// Sort by name. This will keep the route positions relatively stable even though they
// will be repeatedly added and removed.
Collections.sort(mFlatRoutes, sComparator);
notifyDataSetChanged();
}
void initCheckedItems() {
if (mIgnoreUpdates) return;
mListView.clearChoices();
int count = mFlatRoutes.size();
for (int i = 0; i < count; i++){
final RouteInfo route = mFlatRoutes.get(i);
if (route.getGroup() == mPrimary) {
mListView.setItemChecked(i + 1, true);
}
}
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return null;
if (position == 0) {
return mCategory;
} else if (position == getCount() - 1) {
return null; // Done
}
return mFlatRoutes.get(position - 1);
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return 0;
return position;
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public boolean isEnabled(int position) {
return position > 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
return null;
final int viewType = getItemViewType(position);
ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(GROUP_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);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
holder.position = position;
}
if (viewType == VIEW_ROUTE) {
bindItemView(position, holder);
} else if (viewType == VIEW_HEADER) {
bindHeaderView(position, holder);
}
return convertView;
}
void bindItemView(int position, ViewHolder holder) {
RouteInfo info = (RouteInfo) getItem(position);
holder.text1.setText(info.getName());
final CharSequence status = info.getStatus();
if (TextUtils.isEmpty(status)) {
holder.text2.setVisibility(View.GONE);
} else {
holder.text2.setVisibility(View.VISIBLE);
holder.text2.setText(status);
}
}
void bindHeaderView(int position, ViewHolder holder) {
holder.text1.setText(mCategory.getName());
}
@Override
public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
}
@Override
public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
}
@Override
public void onRouteAdded(MediaRouter router, RouteInfo info) {
update();
initCheckedItems();
}
@Override
public void onRouteRemoved(MediaRouter router, RouteInfo info) {
if (info == mPrimary) {
// Can't keep grouping, clean it up.
onDoneGrouping();
} else {
update();
initCheckedItems();
}
}
@Override
public void onRouteChanged(MediaRouter router, RouteInfo info) {
update();
}
@Override
public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index) {
update();
initCheckedItems();
}
@Override
public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
update();
initCheckedItems();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (getItemViewType(position) == VIEW_DONE) {
onDoneGrouping();
return;
}
final ListView lv = (ListView) parent;
final RouteInfo route = mFlatRoutes.get(position - 1);
final boolean checked = lv.isItemChecked(position);
mIgnoreUpdates = true;
RouteGroup oldGroup = route.getGroup();
if (checked && oldGroup != mPrimary) {
// Assumption: in a groupable category oldGroup will never be null.
oldGroup.removeRoute(route);
// If the group is now empty, remove the group too.
if (oldGroup.getRouteCount() == 0) {
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.selectRoute(mRouteTypes, mPrimary);
}
mRouter.removeRouteInt(oldGroup);
}
mPrimary.addRoute(route);
} else if (!checked) {
if (mPrimary.getRouteCount() > 1) {
mPrimary.removeRoute(route);
// In a groupable category this will add the route into its own new group.
mRouter.addRouteInt(route);
} else {
// We're about to remove the last route.
// Don't let this happen, as it would be silly.
// Turn the checkmark back on again. Silly user!
lv.setItemChecked(position, true);
}
}
mIgnoreUpdates = false;
update();
initCheckedItems();
}
}
static class RouteComparator implements Comparator<RouteInfo> {
@Override
public int compare(RouteInfo lhs, RouteInfo rhs) {
return lhs.getName().toString().compareTo(rhs.getName().toString());
}
}
class RouteChooserDialog extends Dialog {
public RouteChooserDialog(Context context, int theme) {
super(context, theme);
}
@Override
public void onBackPressed() {
if (mGroupAdapter != null) {
onDoneGrouping();
} else {
super.onBackPressed();
}
}
}
}

View File

@ -0,0 +1,65 @@
/*
* 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();
}
}

View File

@ -0,0 +1,44 @@
/*
* 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 android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.ImageButton;
public class ImageButtonNoParentPress extends ImageButton {
public ImageButtonNoParentPress(Context context) {
super(context);
}
public ImageButtonNoParentPress(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ImageButtonNoParentPress(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public void setPressed(boolean pressed) {
// Normally parents propagate pressed state to their children.
// We don't want that to happen here; only press if our parent isn't.
super.setPressed(((ViewGroup) getParent()).isPressed() ? false : pressed);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Even though these two point to the same resource, have two states so the drawable will invalidate itself when coming out of pressed state. -->
<item android:state_focused="true" android:state_enabled="false" android:state_pressed="true" android:drawable="@drawable/list_selector_disabled_holo_light" />
<item android:state_focused="true" android:state_enabled="false" android:drawable="@drawable/list_selector_disabled_holo_light" />
<item android:state_focused="true" android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition_holo_light" />
<item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/list_selector_background_transition_holo_light" />
<item android:state_focused="true" android:drawable="@drawable/list_focused_holo" />
<item android:state_activated="true" android:drawable="@android:drawable/list_activated_holo" />
<item android:drawable="@color/transparent" />
</selector>

View File

@ -45,10 +45,4 @@
<ListView android:id="@id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button android:id="@+id/done"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/borderlessButtonStyle"
android:text="@string/media_route_chooser_grouping_done"
android:visibility="gone" />
</LinearLayout>

View File

@ -17,7 +17,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:background="?android:attr/activatedBackgroundIndicator"
android:background="@drawable/item_background_activated_holo_dark"
android:gravity="center_vertical">
<ImageView android:layout_width="56dp"
@ -37,20 +37,25 @@
<TextView android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView android:id="@android:id/text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<!-- TODO Make this not glow when pressed from above, and give it a divider. -->
<ImageButton android:layout_width="56dp"
android:layout_height="56dp"
android:id="@+id/group_button"
android:background="?android:attr/selectableItemBackground"
android:scaleType="center"
android:visibility="gone" />
<com.android.internal.view.ImageButtonNoParentPress
android:layout_width="56dp"
android:layout_height="56dp"
android:id="@+id/expand_button"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_media_group_expand"
android:scaleType="center"
android:visibility="gone" />
</LinearLayout>

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<com.android.internal.view.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical">
<ImageView android:layout_width="56dp"
android:layout_height="56dp"
android:scaleType="center"
android:id="@+id/icon"
android:visibility="gone" />
<LinearLayout android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="left|center_vertical"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
android:paddingRight="?android:attr/listPreferredItemPaddingRight">
<TextView android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView android:id="@android:id/text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="16dp"
android:id="@+id/check"
android:focusable="false"
android:clickable="false" />
</com.android.internal.view.CheckableLinearLayout>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeightSmall"
android:background="#19ffffff"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:gravity="center_vertical">
<TextView android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="marquee"
android:text="@string/media_route_chooser_grouping_done"
android:textAppearance="?android:attr/textAppearanceMedium" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_media_group_collapse"
android:scaleType="center" />
</LinearLayout>

View File

@ -1166,13 +1166,15 @@
<java-symbol type="attr" name="mediaRouteButtonStyle" />
<java-symbol type="attr" name="externalRouteEnabledDrawable" />
<java-symbol type="layout" name="media_route_chooser_layout" />
<java-symbol type="id" name="extended_settings" />
<java-symbol type="id" name="done" />
<java-symbol type="id" name="check" />
<java-symbol type="layout" name="media_route_chooser_layout" />
<java-symbol type="layout" name="media_route_list_item_top_header" />
<java-symbol type="layout" name="media_route_list_item_section_header" />
<java-symbol type="layout" name="media_route_list_item" />
<java-symbol type="id" name="group_button" />
<java-symbol type="layout" name="media_route_list_item_checkable" />
<java-symbol type="layout" name="media_route_list_item_collapse_group" />
<java-symbol type="string" name="bluetooth_a2dp_audio_route_name" />
<!-- From android.policy -->
<java-symbol type="anim" name="app_starting_exit" />

View File

@ -3571,6 +3571,9 @@
<!-- Name of the default audio route category. [CHAR LIMIT=50] -->
<string name="default_audio_route_category_name">System</string>
<!-- Default name of the bluetooth a2dp audio route. [CHAR LIMIT=50] -->
<string name="bluetooth_a2dp_audio_route_name">Bluetooth audio</string>
<!-- "Done" button for MediaRouter chooser dialog when grouping routes. [CHAR LIMIT=NONE] -->
<string name="media_route_chooser_grouping_done">Done</string>
</resources>

View File

@ -237,6 +237,13 @@ public class MediaRouter {
addRoute(info);
}
/**
* @hide Framework use only
*/
public void addRouteInt(RouteInfo info) {
addRoute(info);
}
static void addRoute(RouteInfo info) {
final RouteCategory cat = info.getCategory();
if (!sStatic.mCategories.contains(cat)) {
@ -246,13 +253,10 @@ public class MediaRouter {
if (cat.isGroupable() && !(info instanceof RouteGroup)) {
// Enforce that any added route in a groupable category must be in a group.
final RouteGroup group = new RouteGroup(info.getCategory());
group.addRoute(info);
sStatic.mRoutes.add(group);
dispatchRouteAdded(group);
final int at = group.getRouteCount();
group.addRoute(info);
dispatchRouteGrouped(info, group, at);
info = group;
} else {
sStatic.mRoutes.add(info);
@ -282,13 +286,22 @@ public class MediaRouter {
public void clearUserRoutes() {
for (int i = 0; i < sStatic.mRoutes.size(); i++) {
final RouteInfo info = sStatic.mRoutes.get(i);
if (info instanceof UserRouteInfo) {
// TODO Right now, RouteGroups only ever contain user routes.
// The code below will need to change if this assumption does.
if (info instanceof UserRouteInfo || info instanceof RouteGroup) {
removeRouteAt(i);
i--;
}
}
}
/**
* @hide internal use only
*/
public void removeRouteInt(RouteInfo info) {
removeRoute(info);
}
static void removeRoute(RouteInfo info) {
if (sStatic.mRoutes.remove(info)) {
final RouteCategory removingCat = info.getCategory();
@ -301,6 +314,11 @@ public class MediaRouter {
break;
}
}
if (info == sStatic.mSelectedRoute) {
// Removing the currently selected route? Select the default before we remove it.
// TODO: Be smarter about the route types here; this selects for all valid.
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudio);
}
if (!found) {
sStatic.mCategories.remove(removingCat);
}
@ -321,6 +339,11 @@ public class MediaRouter {
break;
}
}
if (info == sStatic.mSelectedRoute) {
// Removing the currently selected route? Select the default before we remove it.
// TODO: Be smarter about the route types here; this selects for all valid.
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudio);
}
if (!found) {
sStatic.mCategories.remove(removingCat);
}
@ -478,7 +501,8 @@ public class MediaRouter {
static void onA2dpDeviceConnected() {
final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
info.mName = "Bluetooth"; // TODO Fetch the real name of the device
info.mName = sStatic.mResources.getString(
com.android.internal.R.string.bluetooth_a2dp_audio_route_name);
sStatic.mBluetoothA2dpRoute = info;
addRoute(sStatic.mBluetoothA2dpRoute);
}
@ -567,9 +591,9 @@ public class MediaRouter {
@Override
public String toString() {
String supportedTypes = typesToString(mSupportedTypes);
return "RouteInfo{ name=" + mName + ", status=" + mStatus +
" category=" + mCategory +
String supportedTypes = typesToString(getSupportedTypes());
return getClass().getSimpleName() + "{ name=" + getName() + ", status=" + getStatus() +
" category=" + getCategory() +
" supportedTypes=" + supportedTypes + "}";
}
}
@ -698,6 +722,7 @@ public class MediaRouter {
}
final int at = mRoutes.size();
mRoutes.add(route);
route.mGroup = this;
mUpdateName = true;
dispatchRouteGrouped(route, this, at);
routeUpdated();
@ -720,6 +745,7 @@ public class MediaRouter {
" group category=" + mCategory + ")");
}
mRoutes.add(insertAt, route);
route.mGroup = this;
mUpdateName = true;
dispatchRouteGrouped(route, this, insertAt);
routeUpdated();
@ -736,6 +762,7 @@ public class MediaRouter {
" is not a member of this group.");
}
mRoutes.remove(route);
route.mGroup = null;
mUpdateName = true;
dispatchRouteUngrouped(route, this);
routeUpdated();
@ -748,6 +775,7 @@ public class MediaRouter {
*/
public void removeRoute(int index) {
RouteInfo route = mRoutes.remove(index);
route.mGroup = null;
mUpdateName = true;
dispatchRouteUngrouped(route, this);
routeUpdated();
@ -799,6 +827,18 @@ public class MediaRouter {
setStatusInt(status);
}
@Override
void routeUpdated() {
int types = 0;
final int count = mRoutes.size();
for (int i = 0; i < count; i++) {
types |= mRoutes.get(i).mSupportedTypes;
}
mSupportedTypes = types;
mIcon = count == 1 ? mRoutes.get(0).getIconDrawable() : null;
super.routeUpdated();
}
void updateName() {
final StringBuilder sb = new StringBuilder();
final int count = mRoutes.size();
@ -810,6 +850,19 @@ public class MediaRouter {
mName = sb.toString();
mUpdateName = false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(super.toString());
sb.append('[');
final int count = mRoutes.size();
for (int i = 0; i < count; i++) {
if (i > 0) sb.append(", ");
sb.append(mRoutes.get(i));
}
sb.append(']');
return sb.toString();
}
}
/**
@ -884,7 +937,7 @@ public class MediaRouter {
public String toString() {
return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
" groupable=" + mGroupable + " routes=" + sStatic.mRoutes.size() + " }";
" groupable=" + mGroupable + " }";
}
}