diff options
| author | 2012-06-13 23:15:49 -0700 | |
|---|---|---|
| committer | 2012-06-13 23:27:09 -0700 | |
| commit | d6d0bddee363e0c7fe61f63bd9d9864a71d887d6 (patch) | |
| tree | 46040408e1d65a458de7ce2db775d661d910c6a4 | |
| parent | 6a7977794961fa7c165f7963b6ca6d82f94633ce (diff) | |
Support route grouping in the MediaRouter dialog UI.
Change-Id: Idcae12cedfb7ca13950e7fa45441fba2029a9f68
17 files changed, 689 insertions, 49 deletions
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java index 000eee74562d..bfcfdfa86a9b 100644 --- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java +++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java @@ -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(); } - } - private static class ViewHolder { - public TextView text1; - public TextView text2; + 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 class GroupAdapter extends BaseAdapter { + 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(); + } + @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(); + } } } } diff --git a/core/java/com/android/internal/view/CheckableLinearLayout.java b/core/java/com/android/internal/view/CheckableLinearLayout.java new file mode 100644 index 000000000000..3fb7cec0e689 --- /dev/null +++ b/core/java/com/android/internal/view/CheckableLinearLayout.java @@ -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(); + } +} diff --git a/core/java/com/android/internal/view/ImageButtonNoParentPress.java b/core/java/com/android/internal/view/ImageButtonNoParentPress.java new file mode 100644 index 000000000000..a6cfd8612191 --- /dev/null +++ b/core/java/com/android/internal/view/ImageButtonNoParentPress.java @@ -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); + } +} diff --git a/core/res/res/drawable-hdpi/ic_media_group_collapse.png b/core/res/res/drawable-hdpi/ic_media_group_collapse.png Binary files differnew file mode 100644 index 000000000000..89abf2c21ac5 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_media_group_collapse.png diff --git a/core/res/res/drawable-hdpi/ic_media_group_expand.png b/core/res/res/drawable-hdpi/ic_media_group_expand.png Binary files differnew file mode 100644 index 000000000000..d9470b2ef098 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_media_group_expand.png diff --git a/core/res/res/drawable-mdpi/ic_media_group_collapse.png b/core/res/res/drawable-mdpi/ic_media_group_collapse.png Binary files differnew file mode 100644 index 000000000000..34454ac3e7e8 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_media_group_collapse.png diff --git a/core/res/res/drawable-mdpi/ic_media_group_expand.png b/core/res/res/drawable-mdpi/ic_media_group_expand.png Binary files differnew file mode 100644 index 000000000000..8ce5a448d813 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_media_group_expand.png diff --git a/core/res/res/drawable-xhdpi/ic_media_group_collapse.png b/core/res/res/drawable-xhdpi/ic_media_group_collapse.png Binary files differnew file mode 100644 index 000000000000..2fb7428f266f --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_media_group_collapse.png diff --git a/core/res/res/drawable-xhdpi/ic_media_group_expand.png b/core/res/res/drawable-xhdpi/ic_media_group_expand.png Binary files differnew file mode 100644 index 000000000000..5755b9da2f03 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_media_group_expand.png diff --git a/core/res/res/drawable/item_background_activated_holo_dark.xml b/core/res/res/drawable/item_background_activated_holo_dark.xml new file mode 100644 index 000000000000..9cbf6abfac35 --- /dev/null +++ b/core/res/res/drawable/item_background_activated_holo_dark.xml @@ -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> diff --git a/core/res/res/layout/media_route_chooser_layout.xml b/core/res/res/layout/media_route_chooser_layout.xml index 320d6de47ac2..731c0d057630 100644 --- a/core/res/res/layout/media_route_chooser_layout.xml +++ b/core/res/res/layout/media_route_chooser_layout.xml @@ -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> diff --git a/core/res/res/layout/media_route_list_item.xml b/core/res/res/layout/media_route_list_item.xml index d457c6cd2b22..ba1aaf2a2171 100644 --- a/core/res/res/layout/media_route_list_item.xml +++ b/core/res/res/layout/media_route_list_item.xml @@ -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> diff --git a/core/res/res/layout/media_route_list_item_checkable.xml b/core/res/res/layout/media_route_list_item_checkable.xml new file mode 100644 index 000000000000..f6ba09e1e7f8 --- /dev/null +++ b/core/res/res/layout/media_route_list_item_checkable.xml @@ -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> diff --git a/core/res/res/layout/media_route_list_item_collapse_group.xml b/core/res/res/layout/media_route_list_item_collapse_group.xml new file mode 100644 index 000000000000..3f4b1c0843bb --- /dev/null +++ b/core/res/res/layout/media_route_list_item_collapse_group.xml @@ -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> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 0af853443605..a6f2f49c66ae 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -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" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 967d700ba12f..da4d37a8c109 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -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> diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index b0657ff5fb15..8488cd2058fb 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -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 + " }"; } } |