1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.app; 18 19 import com.android.internal.R; 20 21 import android.app.Activity; 22 import android.app.Dialog; 23 import android.app.DialogFragment; 24 import android.app.MediaRouteActionProvider; 25 import android.app.MediaRouteButton; 26 import android.content.Context; 27 import android.graphics.drawable.Drawable; 28 import android.media.AudioManager; 29 import android.media.MediaRouter; 30 import android.media.MediaRouter.RouteCategory; 31 import android.media.MediaRouter.RouteGroup; 32 import android.media.MediaRouter.RouteInfo; 33 import android.os.Bundle; 34 import android.text.TextUtils; 35 import android.view.KeyEvent; 36 import android.view.LayoutInflater; 37 import android.view.View; 38 import android.view.ViewGroup; 39 import android.widget.AdapterView; 40 import android.widget.BaseAdapter; 41 import android.widget.CheckBox; 42 import android.widget.Checkable; 43 import android.widget.ImageButton; 44 import android.widget.ImageView; 45 import android.widget.ListView; 46 import android.widget.SeekBar; 47 import android.widget.TextView; 48 49 import java.util.ArrayList; 50 import java.util.Collections; 51 import java.util.Comparator; 52 import java.util.List; 53 54 /** 55 * This class implements the route chooser dialog for {@link MediaRouter}. 56 * 57 * @see MediaRouteButton 58 * @see MediaRouteActionProvider 59 */ 60 public class MediaRouteChooserDialogFragment extends DialogFragment { 61 private static final String TAG = "MediaRouteChooserDialogFragment"; 62 public static final String FRAGMENT_TAG = "android:MediaRouteChooserDialogFragment"; 63 64 private static final int[] ITEM_LAYOUTS = new int[] { 65 R.layout.media_route_list_item_top_header, 66 R.layout.media_route_list_item_section_header, 67 R.layout.media_route_list_item, 68 R.layout.media_route_list_item_checkable, 69 R.layout.media_route_list_item_collapse_group 70 }; 71 72 MediaRouter mRouter; 73 private int mRouteTypes; 74 75 private LayoutInflater mInflater; 76 private LauncherListener mLauncherListener; 77 private View.OnClickListener mExtendedSettingsListener; 78 private RouteAdapter mAdapter; 79 private ListView mListView; 80 private SeekBar mVolumeSlider; 81 private ImageView mVolumeIcon; 82 83 final RouteComparator mComparator = new RouteComparator(); 84 final MediaRouterCallback mCallback = new MediaRouterCallback(); 85 private boolean mIgnoreSliderVolumeChanges; 86 private boolean mIgnoreCallbackVolumeChanges; 87 MediaRouteChooserDialogFragment()88 public MediaRouteChooserDialogFragment() { 89 setStyle(STYLE_NO_TITLE, R.style.Theme_DeviceDefault_Dialog); 90 } 91 setLauncherListener(LauncherListener listener)92 public void setLauncherListener(LauncherListener listener) { 93 mLauncherListener = listener; 94 } 95 96 @Override onAttach(Activity activity)97 public void onAttach(Activity activity) { 98 super.onAttach(activity); 99 mRouter = (MediaRouter) activity.getSystemService(Context.MEDIA_ROUTER_SERVICE); 100 } 101 102 @Override onDetach()103 public void onDetach() { 104 super.onDetach(); 105 if (mLauncherListener != null) { 106 mLauncherListener.onDetached(this); 107 } 108 if (mAdapter != null) { 109 mAdapter = null; 110 } 111 mInflater = null; 112 mRouter.removeCallback(mCallback); 113 mRouter = null; 114 } 115 setExtendedSettingsClickListener(View.OnClickListener listener)116 public void setExtendedSettingsClickListener(View.OnClickListener listener) { 117 mExtendedSettingsListener = listener; 118 } 119 setRouteTypes(int types)120 public void setRouteTypes(int types) { 121 mRouteTypes = types; 122 } 123 updateVolume()124 void updateVolume() { 125 if (mRouter == null) return; 126 127 final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); 128 mVolumeIcon.setImageResource( 129 selectedRoute.getPlaybackType() == RouteInfo.PLAYBACK_TYPE_LOCAL ? 130 R.drawable.ic_audio_vol : R.drawable.ic_media_route_on_holo_dark); 131 132 mIgnoreSliderVolumeChanges = true; 133 134 if (selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_FIXED) { 135 // Disable the slider and show it at max volume. 136 mVolumeSlider.setMax(1); 137 mVolumeSlider.setProgress(1); 138 mVolumeSlider.setEnabled(false); 139 } else { 140 mVolumeSlider.setEnabled(true); 141 mVolumeSlider.setMax(selectedRoute.getVolumeMax()); 142 mVolumeSlider.setProgress(selectedRoute.getVolume()); 143 } 144 145 mIgnoreSliderVolumeChanges = false; 146 } 147 changeVolume(int newValue)148 void changeVolume(int newValue) { 149 if (mIgnoreSliderVolumeChanges) return; 150 151 final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); 152 if (selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_VARIABLE) { 153 final int maxVolume = selectedRoute.getVolumeMax(); 154 newValue = Math.max(0, Math.min(newValue, maxVolume)); 155 selectedRoute.requestSetVolume(newValue); 156 } 157 } 158 159 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)160 public View onCreateView(LayoutInflater inflater, ViewGroup container, 161 Bundle savedInstanceState) { 162 mInflater = inflater; 163 final View layout = inflater.inflate(R.layout.media_route_chooser_layout, container, false); 164 165 mVolumeIcon = (ImageView) layout.findViewById(R.id.volume_icon); 166 mVolumeSlider = (SeekBar) layout.findViewById(R.id.volume_slider); 167 updateVolume(); 168 mVolumeSlider.setOnSeekBarChangeListener(new VolumeSliderChangeListener()); 169 170 if (mExtendedSettingsListener != null) { 171 final View extendedSettingsButton = layout.findViewById(R.id.extended_settings); 172 extendedSettingsButton.setVisibility(View.VISIBLE); 173 extendedSettingsButton.setOnClickListener(mExtendedSettingsListener); 174 } 175 176 final ListView list = (ListView) layout.findViewById(R.id.list); 177 list.setItemsCanFocus(true); 178 list.setAdapter(mAdapter = new RouteAdapter()); 179 list.setOnItemClickListener(mAdapter); 180 181 mListView = list; 182 mRouter.addCallback(mRouteTypes, mCallback); 183 184 mAdapter.scrollToSelectedItem(); 185 186 return layout; 187 } 188 189 @Override onCreateDialog(Bundle savedInstanceState)190 public Dialog onCreateDialog(Bundle savedInstanceState) { 191 return new RouteChooserDialog(getActivity(), getTheme()); 192 } 193 194 @Override onResume()195 public void onResume() { 196 super.onResume(); 197 } 198 199 private static class ViewHolder { 200 public TextView text1; 201 public TextView text2; 202 public ImageView icon; 203 public ImageButton expandGroupButton; 204 public RouteAdapter.ExpandGroupListener expandGroupListener; 205 public int position; 206 public CheckBox check; 207 } 208 209 private class RouteAdapter extends BaseAdapter implements ListView.OnItemClickListener { 210 private static final int VIEW_TOP_HEADER = 0; 211 private static final int VIEW_SECTION_HEADER = 1; 212 private static final int VIEW_ROUTE = 2; 213 private static final int VIEW_GROUPING_ROUTE = 3; 214 private static final int VIEW_GROUPING_DONE = 4; 215 216 private int mSelectedItemPosition = -1; 217 private final ArrayList<Object> mItems = new ArrayList<Object>(); 218 219 private RouteCategory mCategoryEditingGroups; 220 private RouteGroup mEditingGroup; 221 222 // Temporary lists for manipulation 223 private final ArrayList<RouteInfo> mCatRouteList = new ArrayList<RouteInfo>(); 224 private final ArrayList<RouteInfo> mSortRouteList = new ArrayList<RouteInfo>(); 225 226 private boolean mIgnoreUpdates; 227 RouteAdapter()228 RouteAdapter() { 229 update(); 230 } 231 update()232 void update() { 233 /* 234 * This is kind of wacky, but our data sets are going to be 235 * fairly small on average. Ideally we should be able to do some of this stuff 236 * in-place instead. 237 * 238 * Basic idea: each entry in mItems represents an item in the list for quick access. 239 * Entries can be a RouteCategory (section header), a RouteInfo with a category of 240 * mCategoryEditingGroups (a flattened RouteInfo pulled out of its group, allowing 241 * the user to change the group), 242 */ 243 if (mIgnoreUpdates) return; 244 245 mItems.clear(); 246 247 final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); 248 mSelectedItemPosition = -1; 249 250 List<RouteInfo> routes; 251 final int catCount = mRouter.getCategoryCount(); 252 for (int i = 0; i < catCount; i++) { 253 final RouteCategory cat = mRouter.getCategoryAt(i); 254 routes = cat.getRoutes(mCatRouteList); 255 256 mItems.add(cat); 257 258 if (cat == mCategoryEditingGroups) { 259 addGroupEditingCategoryRoutes(routes); 260 } else { 261 addSelectableRoutes(selectedRoute, routes); 262 } 263 264 routes.clear(); 265 } 266 267 notifyDataSetChanged(); 268 if (mListView != null && mSelectedItemPosition >= 0) { 269 mListView.setItemChecked(mSelectedItemPosition, true); 270 } 271 } 272 scrollToEditingGroup()273 void scrollToEditingGroup() { 274 if (mCategoryEditingGroups == null || mListView == null) return; 275 276 int pos = 0; 277 int bound = 0; 278 final int itemCount = mItems.size(); 279 for (int i = 0; i < itemCount; i++) { 280 final Object item = mItems.get(i); 281 if (item != null && item == mCategoryEditingGroups) { 282 bound = i; 283 } 284 if (item == null) { 285 pos = i; 286 break; // this is always below the category header; we can stop here. 287 } 288 } 289 290 mListView.smoothScrollToPosition(pos, bound); 291 } 292 scrollToSelectedItem()293 void scrollToSelectedItem() { 294 if (mListView == null || mSelectedItemPosition < 0) return; 295 296 mListView.smoothScrollToPosition(mSelectedItemPosition); 297 } 298 addSelectableRoutes(RouteInfo selectedRoute, List<RouteInfo> from)299 void addSelectableRoutes(RouteInfo selectedRoute, List<RouteInfo> from) { 300 final int routeCount = from.size(); 301 for (int j = 0; j < routeCount; j++) { 302 final RouteInfo info = from.get(j); 303 if (info == selectedRoute) { 304 mSelectedItemPosition = mItems.size(); 305 } 306 mItems.add(info); 307 } 308 } 309 addGroupEditingCategoryRoutes(List<RouteInfo> from)310 void addGroupEditingCategoryRoutes(List<RouteInfo> from) { 311 // Unpack groups and flatten for presentation 312 // mSortRouteList will always be empty here. 313 final int topCount = from.size(); 314 for (int i = 0; i < topCount; i++) { 315 final RouteInfo route = from.get(i); 316 final RouteGroup group = route.getGroup(); 317 if (group == route) { 318 // This is a group, unpack it. 319 final int groupCount = group.getRouteCount(); 320 for (int j = 0; j < groupCount; j++) { 321 final RouteInfo innerRoute = group.getRouteAt(j); 322 mSortRouteList.add(innerRoute); 323 } 324 } else { 325 mSortRouteList.add(route); 326 } 327 } 328 // Sort by name. This will keep the route positions relatively stable even though they 329 // will be repeatedly added and removed. 330 Collections.sort(mSortRouteList, mComparator); 331 332 mItems.addAll(mSortRouteList); 333 mSortRouteList.clear(); 334 335 mItems.add(null); // Sentinel reserving space for the "done" button. 336 } 337 338 @Override getCount()339 public int getCount() { 340 return mItems.size(); 341 } 342 343 @Override getViewTypeCount()344 public int getViewTypeCount() { 345 return 5; 346 } 347 348 @Override getItemViewType(int position)349 public int getItemViewType(int position) { 350 final Object item = getItem(position); 351 if (item instanceof RouteCategory) { 352 return position == 0 ? VIEW_TOP_HEADER : VIEW_SECTION_HEADER; 353 } else if (item == null) { 354 return VIEW_GROUPING_DONE; 355 } else { 356 final RouteInfo info = (RouteInfo) item; 357 if (info.getCategory() == mCategoryEditingGroups) { 358 return VIEW_GROUPING_ROUTE; 359 } 360 return VIEW_ROUTE; 361 } 362 } 363 364 @Override areAllItemsEnabled()365 public boolean areAllItemsEnabled() { 366 return false; 367 } 368 369 @Override isEnabled(int position)370 public boolean isEnabled(int position) { 371 switch (getItemViewType(position)) { 372 case VIEW_ROUTE: 373 case VIEW_GROUPING_ROUTE: 374 case VIEW_GROUPING_DONE: 375 return true; 376 default: 377 return false; 378 } 379 } 380 381 @Override getItem(int position)382 public Object getItem(int position) { 383 return mItems.get(position); 384 } 385 386 @Override getItemId(int position)387 public long getItemId(int position) { 388 return position; 389 } 390 391 @Override getView(int position, View convertView, ViewGroup parent)392 public View getView(int position, View convertView, ViewGroup parent) { 393 final int viewType = getItemViewType(position); 394 395 ViewHolder holder; 396 if (convertView == null) { 397 convertView = mInflater.inflate(ITEM_LAYOUTS[viewType], parent, false); 398 holder = new ViewHolder(); 399 holder.position = position; 400 holder.text1 = (TextView) convertView.findViewById(R.id.text1); 401 holder.text2 = (TextView) convertView.findViewById(R.id.text2); 402 holder.icon = (ImageView) convertView.findViewById(R.id.icon); 403 holder.check = (CheckBox) convertView.findViewById(R.id.check); 404 holder.expandGroupButton = (ImageButton) convertView.findViewById( 405 R.id.expand_button); 406 if (holder.expandGroupButton != null) { 407 holder.expandGroupListener = new ExpandGroupListener(); 408 holder.expandGroupButton.setOnClickListener(holder.expandGroupListener); 409 } 410 411 final View fview = convertView; 412 final ListView list = (ListView) parent; 413 final ViewHolder fholder = holder; 414 convertView.setOnClickListener(new View.OnClickListener() { 415 @Override public void onClick(View v) { 416 list.performItemClick(fview, fholder.position, 0); 417 } 418 }); 419 convertView.setTag(holder); 420 } else { 421 holder = (ViewHolder) convertView.getTag(); 422 holder.position = position; 423 } 424 425 switch (viewType) { 426 case VIEW_ROUTE: 427 case VIEW_GROUPING_ROUTE: 428 bindItemView(position, holder); 429 break; 430 case VIEW_SECTION_HEADER: 431 case VIEW_TOP_HEADER: 432 bindHeaderView(position, holder); 433 break; 434 } 435 436 convertView.setActivated(position == mSelectedItemPosition); 437 438 return convertView; 439 } 440 bindItemView(int position, ViewHolder holder)441 void bindItemView(int position, ViewHolder holder) { 442 RouteInfo info = (RouteInfo) mItems.get(position); 443 holder.text1.setText(info.getName(getActivity())); 444 final CharSequence status = info.getStatus(); 445 if (TextUtils.isEmpty(status)) { 446 holder.text2.setVisibility(View.GONE); 447 } else { 448 holder.text2.setVisibility(View.VISIBLE); 449 holder.text2.setText(status); 450 } 451 Drawable icon = info.getIconDrawable(); 452 if (icon != null) { 453 // Make sure we have a fresh drawable where it doesn't matter if we mutate it 454 icon = icon.getConstantState().newDrawable(getResources()); 455 } 456 holder.icon.setImageDrawable(icon); 457 holder.icon.setVisibility(icon != null ? View.VISIBLE : View.GONE); 458 459 RouteCategory cat = info.getCategory(); 460 boolean canGroup = false; 461 if (cat == mCategoryEditingGroups) { 462 RouteGroup group = info.getGroup(); 463 holder.check.setEnabled(group.getRouteCount() > 1); 464 holder.check.setChecked(group == mEditingGroup); 465 } else { 466 if (cat.isGroupable()) { 467 final RouteGroup group = (RouteGroup) info; 468 canGroup = group.getRouteCount() > 1 || 469 getItemViewType(position - 1) == VIEW_ROUTE || 470 (position < getCount() - 1 && 471 getItemViewType(position + 1) == VIEW_ROUTE); 472 } 473 } 474 475 if (holder.expandGroupButton != null) { 476 holder.expandGroupButton.setVisibility(canGroup ? View.VISIBLE : View.GONE); 477 holder.expandGroupListener.position = position; 478 } 479 } 480 bindHeaderView(int position, ViewHolder holder)481 void bindHeaderView(int position, ViewHolder holder) { 482 RouteCategory cat = (RouteCategory) mItems.get(position); 483 holder.text1.setText(cat.getName(getActivity())); 484 } 485 486 @Override onItemClick(AdapterView<?> parent, View view, int position, long id)487 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 488 final int type = getItemViewType(position); 489 if (type == VIEW_SECTION_HEADER || type == VIEW_TOP_HEADER) { 490 return; 491 } else if (type == VIEW_GROUPING_DONE) { 492 finishGrouping(); 493 return; 494 } else { 495 final Object item = getItem(position); 496 if (!(item instanceof RouteInfo)) { 497 // Oops. Stale event running around? Skip it. 498 return; 499 } 500 501 final RouteInfo route = (RouteInfo) item; 502 if (type == VIEW_ROUTE) { 503 mRouter.selectRouteInt(mRouteTypes, route); 504 dismiss(); 505 } else if (type == VIEW_GROUPING_ROUTE) { 506 final Checkable c = (Checkable) view; 507 final boolean wasChecked = c.isChecked(); 508 509 mIgnoreUpdates = true; 510 RouteGroup oldGroup = route.getGroup(); 511 if (!wasChecked && oldGroup != mEditingGroup) { 512 // Assumption: in a groupable category oldGroup will never be null. 513 if (mRouter.getSelectedRoute(mRouteTypes) == oldGroup) { 514 // Old group was selected but is now empty. Select the group 515 // we're manipulating since that's where the last route went. 516 mRouter.selectRouteInt(mRouteTypes, mEditingGroup); 517 } 518 oldGroup.removeRoute(route); 519 mEditingGroup.addRoute(route); 520 c.setChecked(true); 521 } else if (wasChecked && mEditingGroup.getRouteCount() > 1) { 522 mEditingGroup.removeRoute(route); 523 524 // In a groupable category this will add 525 // the route into its own new group. 526 mRouter.addRouteInt(route); 527 } 528 mIgnoreUpdates = false; 529 update(); 530 } 531 } 532 } 533 isGrouping()534 boolean isGrouping() { 535 return mCategoryEditingGroups != null; 536 } 537 finishGrouping()538 void finishGrouping() { 539 mCategoryEditingGroups = null; 540 mEditingGroup = null; 541 getDialog().setCanceledOnTouchOutside(true); 542 update(); 543 scrollToSelectedItem(); 544 } 545 546 class ExpandGroupListener implements View.OnClickListener { 547 int position; 548 549 @Override onClick(View v)550 public void onClick(View v) { 551 // Assumption: this is only available for the user to click if we're presenting 552 // a groupable category, where every top-level route in the category is a group. 553 final RouteGroup group = (RouteGroup) getItem(position); 554 mEditingGroup = group; 555 mCategoryEditingGroups = group.getCategory(); 556 getDialog().setCanceledOnTouchOutside(false); 557 mRouter.selectRouteInt(mRouteTypes, mEditingGroup); 558 update(); 559 scrollToEditingGroup(); 560 } 561 } 562 } 563 564 class MediaRouterCallback extends MediaRouter.Callback { 565 @Override onRouteSelected(MediaRouter router, int type, RouteInfo info)566 public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { 567 mAdapter.update(); 568 updateVolume(); 569 } 570 571 @Override onRouteUnselected(MediaRouter router, int type, RouteInfo info)572 public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { 573 mAdapter.update(); 574 } 575 576 @Override onRouteAdded(MediaRouter router, RouteInfo info)577 public void onRouteAdded(MediaRouter router, RouteInfo info) { 578 mAdapter.update(); 579 } 580 581 @Override onRouteRemoved(MediaRouter router, RouteInfo info)582 public void onRouteRemoved(MediaRouter router, RouteInfo info) { 583 if (info == mAdapter.mEditingGroup) { 584 mAdapter.finishGrouping(); 585 } 586 mAdapter.update(); 587 } 588 589 @Override onRouteChanged(MediaRouter router, RouteInfo info)590 public void onRouteChanged(MediaRouter router, RouteInfo info) { 591 mAdapter.notifyDataSetChanged(); 592 } 593 594 @Override onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index)595 public void onRouteGrouped(MediaRouter router, RouteInfo info, 596 RouteGroup group, int index) { 597 mAdapter.update(); 598 } 599 600 @Override onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group)601 public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { 602 mAdapter.update(); 603 } 604 605 @Override onRouteVolumeChanged(MediaRouter router, RouteInfo info)606 public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) { 607 if (!mIgnoreCallbackVolumeChanges) { 608 updateVolume(); 609 } 610 } 611 } 612 613 class RouteComparator implements Comparator<RouteInfo> { 614 @Override compare(RouteInfo lhs, RouteInfo rhs)615 public int compare(RouteInfo lhs, RouteInfo rhs) { 616 return lhs.getName(getActivity()).toString() 617 .compareTo(rhs.getName(getActivity()).toString()); 618 } 619 } 620 621 class RouteChooserDialog extends Dialog { RouteChooserDialog(Context context, int theme)622 public RouteChooserDialog(Context context, int theme) { 623 super(context, theme); 624 } 625 626 @Override onBackPressed()627 public void onBackPressed() { 628 if (mAdapter != null && mAdapter.isGrouping()) { 629 mAdapter.finishGrouping(); 630 } else { 631 super.onBackPressed(); 632 } 633 } 634 onKeyDown(int keyCode, KeyEvent event)635 public boolean onKeyDown(int keyCode, KeyEvent event) { 636 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) { 637 mRouter.getSelectedRoute(mRouteTypes).requestUpdateVolume(-1); 638 return true; 639 } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) { 640 mRouter.getSelectedRoute(mRouteTypes).requestUpdateVolume(1); 641 return true; 642 } else { 643 return super.onKeyDown(keyCode, event); 644 } 645 } 646 onKeyUp(int keyCode, KeyEvent event)647 public boolean onKeyUp(int keyCode, KeyEvent event) { 648 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) { 649 return true; 650 } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) { 651 return true; 652 } else { 653 return super.onKeyUp(keyCode, event); 654 } 655 } 656 } 657 658 /** 659 * Implemented by the MediaRouteButton that launched this dialog 660 */ 661 public interface LauncherListener { onDetached(MediaRouteChooserDialogFragment detachedFragment)662 public void onDetached(MediaRouteChooserDialogFragment detachedFragment); 663 } 664 665 class VolumeSliderChangeListener implements SeekBar.OnSeekBarChangeListener { 666 667 @Override onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)668 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 669 changeVolume(progress); 670 } 671 672 @Override onStartTrackingTouch(SeekBar seekBar)673 public void onStartTrackingTouch(SeekBar seekBar) { 674 mIgnoreCallbackVolumeChanges = true; 675 } 676 677 @Override onStopTrackingTouch(SeekBar seekBar)678 public void onStopTrackingTouch(SeekBar seekBar) { 679 mIgnoreCallbackVolumeChanges = false; 680 updateVolume(); 681 } 682 683 } 684 } 685