1 /* 2 * Copyright (C) 2020 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.systemui.media.dialog; 18 19 import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_GO_TO_APP; 20 import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE; 21 import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; 22 23 import android.annotation.DrawableRes; 24 import android.content.Context; 25 import android.content.res.ColorStateList; 26 import android.graphics.drawable.AnimatedVectorDrawable; 27 import android.graphics.drawable.Drawable; 28 import android.util.Log; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.accessibility.AccessibilityNodeInfo; 32 import android.widget.CheckBox; 33 import android.widget.TextView; 34 35 import androidx.annotation.DoNotInline; 36 import androidx.annotation.NonNull; 37 import androidx.annotation.RequiresApi; 38 import androidx.core.widget.CompoundButtonCompat; 39 import androidx.recyclerview.widget.RecyclerView; 40 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.settingslib.media.LocalMediaManager.MediaDeviceState; 43 import com.android.settingslib.media.MediaDevice; 44 import com.android.systemui.res.R; 45 46 import java.util.List; 47 import java.util.concurrent.CopyOnWriteArrayList; 48 49 /** 50 * Adapter for media output dialog. 51 */ 52 public class MediaOutputAdapter extends MediaOutputBaseAdapter { 53 54 private static final String TAG = "MediaOutputAdapter"; 55 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 56 private static final float DEVICE_DISCONNECTED_ALPHA = 0.5f; 57 private static final float DEVICE_CONNECTED_ALPHA = 1f; 58 protected List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>(); 59 MediaOutputAdapter(MediaOutputController controller)60 public MediaOutputAdapter(MediaOutputController controller) { 61 super(controller); 62 setHasStableIds(true); 63 } 64 65 @Override updateItems()66 public void updateItems() { 67 mMediaItemList.clear(); 68 mMediaItemList.addAll(mController.getMediaItemList()); 69 notifyDataSetChanged(); 70 } 71 72 @Override onCreateViewHolder(@onNull ViewGroup viewGroup, int viewType)73 public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, 74 int viewType) { 75 super.onCreateViewHolder(viewGroup, viewType); 76 switch (viewType) { 77 case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER: 78 return new MediaGroupDividerViewHolder(mHolderView); 79 case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE: 80 case MediaItem.MediaItemType.TYPE_DEVICE: 81 default: 82 return new MediaDeviceViewHolder(mHolderView); 83 } 84 } 85 86 @Override onBindViewHolder(@onNull RecyclerView.ViewHolder viewHolder, int position)87 public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { 88 if (position >= mMediaItemList.size()) { 89 if (DEBUG) { 90 Log.d(TAG, "Incorrect position: " + position + " list size: " 91 + mMediaItemList.size()); 92 } 93 return; 94 } 95 MediaItem currentMediaItem = mMediaItemList.get(position); 96 switch (currentMediaItem.getMediaItemType()) { 97 case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER: 98 ((MediaGroupDividerViewHolder) viewHolder).onBind(currentMediaItem.getTitle()); 99 break; 100 case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE: 101 ((MediaDeviceViewHolder) viewHolder).onBind(CUSTOMIZED_ITEM_PAIR_NEW); 102 break; 103 case MediaItem.MediaItemType.TYPE_DEVICE: 104 ((MediaDeviceViewHolder) viewHolder).onBind( 105 currentMediaItem.getMediaDevice().get(), 106 position); 107 break; 108 default: 109 Log.d(TAG, "Incorrect position: " + position); 110 } 111 } 112 113 @Override getItemId(int position)114 public long getItemId(int position) { 115 if (position >= mMediaItemList.size()) { 116 Log.d(TAG, "Incorrect position for item id: " + position); 117 return position; 118 } 119 MediaItem currentMediaItem = mMediaItemList.get(position); 120 return currentMediaItem.getMediaDevice().isPresent() 121 ? currentMediaItem.getMediaDevice().get().getId().hashCode() 122 : position; 123 } 124 125 @Override getItemViewType(int position)126 public int getItemViewType(int position) { 127 if (position >= mMediaItemList.size()) { 128 Log.d(TAG, "Incorrect position for item type: " + position); 129 return MediaItem.MediaItemType.TYPE_GROUP_DIVIDER; 130 } 131 return mMediaItemList.get(position).getMediaItemType(); 132 } 133 134 @Override getItemCount()135 public int getItemCount() { 136 return mMediaItemList.size(); 137 } 138 139 class MediaDeviceViewHolder extends MediaDeviceBaseViewHolder { 140 MediaDeviceViewHolder(View view)141 MediaDeviceViewHolder(View view) { 142 super(view); 143 } 144 145 @Override onBind(MediaDevice device, int position)146 void onBind(MediaDevice device, int position) { 147 super.onBind(device, position); 148 boolean isMutingExpectedDeviceExist = mController.hasMutingExpectedDevice(); 149 final boolean currentlyConnected = isCurrentlyConnected(device); 150 boolean isCurrentSeekbarInvisible = mSeekBar.getVisibility() == View.GONE; 151 if (mCurrentActivePosition == position) { 152 mCurrentActivePosition = -1; 153 } 154 mStatusIcon.setVisibility(View.GONE); 155 enableFocusPropertyForView(mContainerLayout); 156 157 if (mController.isAnyDeviceTransferring()) { 158 if (device.getState() == MediaDeviceState.STATE_CONNECTING 159 && !mController.hasAdjustVolumeUserRestriction()) { 160 setUpDeviceIcon(device); 161 updateProgressBarColor(); 162 setSingleLineLayout(getItemTitle(device), false /* showSeekBar*/, 163 true /* showProgressBar */, false /* showCheckBox */, 164 false /* showEndTouchArea */); 165 } else { 166 setUpDeviceIcon(device); 167 setSingleLineLayout(getItemTitle(device)); 168 } 169 } else { 170 // Set different layout for each device 171 if (device.isMutingExpectedDevice() 172 && !mController.isCurrentConnectedDeviceRemote()) { 173 updateTitleIcon(R.drawable.media_output_icon_volume, 174 mController.getColorItemContent()); 175 mCurrentActivePosition = position; 176 updateFullItemClickListener(v -> onItemClick(v, device)); 177 setSingleLineLayout(getItemTitle(device)); 178 initFakeActiveDevice(); 179 } else if (device.hasSubtext()) { 180 boolean isActiveWithOngoingSession = 181 (device.hasOngoingSession() && (currentlyConnected || isDeviceIncluded( 182 mController.getSelectedMediaDevice(), device))); 183 boolean isHost = device.isHostForOngoingSession() 184 && isActiveWithOngoingSession; 185 if (isActiveWithOngoingSession) { 186 mCurrentActivePosition = position; 187 updateTitleIcon(R.drawable.media_output_icon_volume, 188 mController.getColorItemContent()); 189 mSubTitleText.setText(device.getSubtextString()); 190 updateTwoLineLayoutContentAlpha(DEVICE_CONNECTED_ALPHA); 191 updateEndClickAreaAsSessionEditing(device, 192 isHost ? R.drawable.media_output_status_edit_session 193 : R.drawable.ic_sound_bars_anim); 194 setTwoLineLayout(device, null /* title */, true /* bFocused */, 195 true /* showSeekBar */, false /* showProgressBar */, 196 true /* showSubtitle */, false /* showStatus */, 197 true /* showEndTouchArea */, false /* isFakeActive */); 198 initSeekbar(device, isCurrentSeekbarInvisible); 199 } else { 200 if (currentlyConnected) { 201 mCurrentActivePosition = position; 202 updateTitleIcon(R.drawable.media_output_icon_volume, 203 mController.getColorItemContent()); 204 initSeekbar(device, isCurrentSeekbarInvisible); 205 } else { 206 setUpDeviceIcon(device); 207 } 208 mSubTitleText.setText(device.getSubtextString()); 209 Drawable deviceStatusIcon = 210 device.hasOngoingSession() ? mContext.getDrawable( 211 R.drawable.ic_sound_bars_anim) 212 : Api34Impl.getDeviceStatusIconBasedOnSelectionBehavior( 213 device, 214 mContext); 215 if (deviceStatusIcon != null) { 216 updateDeviceStatusIcon(deviceStatusIcon); 217 } 218 updateTwoLineLayoutContentAlpha( 219 updateClickActionBasedOnSelectionBehavior(device) 220 ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA); 221 setTwoLineLayout(device, currentlyConnected /* bFocused */, 222 currentlyConnected /* showSeekBar */, 223 false /* showProgressBar */, true /* showSubtitle */, 224 deviceStatusIcon != null /* showStatus */, 225 false /* isFakeActive */); 226 } 227 } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) { 228 setUpDeviceIcon(device); 229 updateConnectionFailedStatusIcon(); 230 mSubTitleText.setText(R.string.media_output_dialog_connect_failed); 231 updateFullItemClickListener(v -> onItemClick(v, device)); 232 setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */, 233 false /* showProgressBar */, true /* showSubtitle */, 234 true /* showStatus */, false /*isFakeActive*/); 235 } else if (device.getState() == MediaDeviceState.STATE_GROUPING) { 236 setUpDeviceIcon(device); 237 updateProgressBarColor(); 238 setSingleLineLayout(getItemTitle(device), false /* showSeekBar*/, 239 true /* showProgressBar */, false /* showCheckBox */, 240 false /* showEndTouchArea */); 241 } else if (mController.getSelectedMediaDevice().size() > 1 242 && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) { 243 // selected device in group 244 boolean isDeviceDeselectable = isDeviceIncluded( 245 mController.getDeselectableMediaDevice(), device); 246 updateTitleIcon(R.drawable.media_output_icon_volume, 247 mController.getColorItemContent()); 248 updateGroupableCheckBox(true, isDeviceDeselectable, device); 249 updateEndClickArea(device, isDeviceDeselectable); 250 disableFocusPropertyForView(mContainerLayout); 251 setUpContentDescriptionForView(mSeekBar, device); 252 setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, 253 false /* showProgressBar */, true /* showCheckBox */, 254 true /* showEndTouchArea */); 255 initSeekbar(device, isCurrentSeekbarInvisible); 256 } else if (!mController.hasAdjustVolumeUserRestriction() 257 && currentlyConnected) { 258 // single selected device 259 if (isMutingExpectedDeviceExist 260 && !mController.isCurrentConnectedDeviceRemote()) { 261 // mark as disconnected and set special click listener 262 setUpDeviceIcon(device); 263 updateFullItemClickListener(v -> cancelMuteAwaitConnection()); 264 setSingleLineLayout(getItemTitle(device)); 265 } else if (device.hasOngoingSession()) { 266 mCurrentActivePosition = position; 267 updateTitleIcon(R.drawable.media_output_icon_volume, 268 mController.getColorItemContent()); 269 updateEndClickAreaAsSessionEditing(device, device.isHostForOngoingSession() 270 ? R.drawable.media_output_status_edit_session 271 : R.drawable.ic_sound_bars_anim); 272 mEndClickIcon.setVisibility(View.VISIBLE); 273 setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, 274 false /* showProgressBar */, false /* showCheckBox */, 275 true /* showEndTouchArea */); 276 initSeekbar(device, isCurrentSeekbarInvisible); 277 } else if (mController.isCurrentConnectedDeviceRemote() 278 && !mController.getSelectableMediaDevice().isEmpty()) { 279 //If device is connected and there's other selectable devices, layout as 280 // one of selected devices. 281 updateTitleIcon(R.drawable.media_output_icon_volume, 282 mController.getColorItemContent()); 283 boolean isDeviceDeselectable = isDeviceIncluded( 284 mController.getDeselectableMediaDevice(), device); 285 updateGroupableCheckBox(true, isDeviceDeselectable, device); 286 updateEndClickArea(device, isDeviceDeselectable); 287 disableFocusPropertyForView(mContainerLayout); 288 setUpContentDescriptionForView(mSeekBar, device); 289 setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, 290 false /* showProgressBar */, true /* showCheckBox */, 291 true /* showEndTouchArea */); 292 initSeekbar(device, isCurrentSeekbarInvisible); 293 } else { 294 updateTitleIcon(R.drawable.media_output_icon_volume, 295 mController.getColorItemContent()); 296 disableFocusPropertyForView(mContainerLayout); 297 setUpContentDescriptionForView(mSeekBar, device); 298 mCurrentActivePosition = position; 299 setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, 300 false /* showProgressBar */, false /* showCheckBox */, 301 false /* showEndTouchArea */); 302 initSeekbar(device, isCurrentSeekbarInvisible); 303 } 304 } else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) { 305 //groupable device 306 setUpDeviceIcon(device); 307 updateGroupableCheckBox(false, true, device); 308 updateEndClickArea(device, true); 309 updateFullItemClickListener(v -> onItemClick(v, device)); 310 setSingleLineLayout(getItemTitle(device), false /* showSeekBar */, 311 false /* showProgressBar */, true /* showCheckBox */, 312 true /* showEndTouchArea */); 313 } else { 314 setUpDeviceIcon(device); 315 setSingleLineLayout(getItemTitle(device)); 316 Drawable deviceStatusIcon = 317 device.hasOngoingSession() ? mContext.getDrawable( 318 R.drawable.ic_sound_bars_anim) 319 : Api34Impl.getDeviceStatusIconBasedOnSelectionBehavior( 320 device, 321 mContext); 322 if (deviceStatusIcon != null) { 323 updateDeviceStatusIcon(deviceStatusIcon); 324 mStatusIcon.setVisibility(View.VISIBLE); 325 } 326 updateSingleLineLayoutContentAlpha( 327 updateClickActionBasedOnSelectionBehavior(device) 328 ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA); 329 } 330 } 331 } 332 setCheckBoxColor(CheckBox checkBox, int color)333 public void setCheckBoxColor(CheckBox checkBox, int color) { 334 int[][] states = {{android.R.attr.state_checked}, {}}; 335 int[] colors = {color, color}; 336 CompoundButtonCompat.setButtonTintList(checkBox, new 337 ColorStateList(states, colors)); 338 } 339 updateTwoLineLayoutContentAlpha(float alphaValue)340 private void updateTwoLineLayoutContentAlpha(float alphaValue) { 341 mSubTitleText.setAlpha(alphaValue); 342 mTitleIcon.setAlpha(alphaValue); 343 mTwoLineTitleText.setAlpha(alphaValue); 344 mStatusIcon.setAlpha(alphaValue); 345 } 346 updateSingleLineLayoutContentAlpha(float alphaValue)347 private void updateSingleLineLayoutContentAlpha(float alphaValue) { 348 mTitleIcon.setAlpha(alphaValue); 349 mTitleText.setAlpha(alphaValue); 350 mStatusIcon.setAlpha(alphaValue); 351 } 352 updateEndClickAreaAsSessionEditing(MediaDevice device, @DrawableRes int id)353 private void updateEndClickAreaAsSessionEditing(MediaDevice device, @DrawableRes int id) { 354 mEndClickIcon.setOnClickListener(null); 355 mEndTouchArea.setOnClickListener(null); 356 updateEndClickAreaColor(mController.getColorSeekbarProgress()); 357 mEndClickIcon.setImageTintList( 358 ColorStateList.valueOf(mController.getColorItemContent())); 359 mEndClickIcon.setOnClickListener( 360 v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v)); 361 mEndTouchArea.setOnClickListener(v -> mEndClickIcon.performClick()); 362 Drawable drawable = mContext.getDrawable(id); 363 mEndClickIcon.setImageDrawable(drawable); 364 if (drawable instanceof AnimatedVectorDrawable) { 365 ((AnimatedVectorDrawable) drawable).start(); 366 } 367 } 368 updateEndClickAreaColor(int color)369 public void updateEndClickAreaColor(int color) { 370 mEndTouchArea.setBackgroundTintList( 371 ColorStateList.valueOf(color)); 372 } 373 updateClickActionBasedOnSelectionBehavior(MediaDevice device)374 private boolean updateClickActionBasedOnSelectionBehavior(MediaDevice device) { 375 View.OnClickListener clickListener = Api34Impl.getClickListenerBasedOnSelectionBehavior( 376 device, mController, v -> onItemClick(v, device)); 377 updateFullItemClickListener(clickListener); 378 return clickListener != null; 379 } 380 updateConnectionFailedStatusIcon()381 private void updateConnectionFailedStatusIcon() { 382 mStatusIcon.setImageDrawable( 383 mContext.getDrawable(R.drawable.media_output_status_failed)); 384 mStatusIcon.setImageTintList( 385 ColorStateList.valueOf(mController.getColorItemContent())); 386 } 387 updateDeviceStatusIcon(Drawable drawable)388 private void updateDeviceStatusIcon(Drawable drawable) { 389 mStatusIcon.setImageDrawable(drawable); 390 mStatusIcon.setImageTintList( 391 ColorStateList.valueOf(mController.getColorItemContent())); 392 if (drawable instanceof AnimatedVectorDrawable) { 393 ((AnimatedVectorDrawable) drawable).start(); 394 } 395 } 396 updateProgressBarColor()397 private void updateProgressBarColor() { 398 mProgressBar.getIndeterminateDrawable().setTintList( 399 ColorStateList.valueOf(mController.getColorItemContent())); 400 } 401 updateEndClickArea(MediaDevice device, boolean isDeviceDeselectable)402 public void updateEndClickArea(MediaDevice device, boolean isDeviceDeselectable) { 403 mEndTouchArea.setOnClickListener(null); 404 mEndTouchArea.setOnClickListener( 405 isDeviceDeselectable ? (v) -> mCheckBox.performClick() : null); 406 mEndTouchArea.setImportantForAccessibility( 407 View.IMPORTANT_FOR_ACCESSIBILITY_YES); 408 mEndTouchArea.setBackgroundTintList( 409 ColorStateList.valueOf(mController.getColorItemBackground())); 410 setUpContentDescriptionForView(mEndTouchArea, device); 411 } 412 updateGroupableCheckBox(boolean isSelected, boolean isGroupable, MediaDevice device)413 private void updateGroupableCheckBox(boolean isSelected, boolean isGroupable, 414 MediaDevice device) { 415 mCheckBox.setOnCheckedChangeListener(null); 416 mCheckBox.setChecked(isSelected); 417 mCheckBox.setOnCheckedChangeListener( 418 isGroupable ? (buttonView, isChecked) -> onGroupActionTriggered(!isSelected, 419 device) : null); 420 mCheckBox.setEnabled(isGroupable); 421 setCheckBoxColor(mCheckBox, mController.getColorItemContent()); 422 } 423 updateFullItemClickListener(View.OnClickListener listener)424 private void updateFullItemClickListener(View.OnClickListener listener) { 425 mContainerLayout.setOnClickListener(listener); 426 updateIconAreaClickListener(listener); 427 } 428 429 @Override onBind(int customizedItem)430 void onBind(int customizedItem) { 431 if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) { 432 mTitleText.setTextColor(mController.getColorItemContent()); 433 mCheckBox.setVisibility(View.GONE); 434 setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new)); 435 final Drawable addDrawable = mContext.getDrawable(R.drawable.ic_add); 436 mTitleIcon.setImageDrawable(addDrawable); 437 mTitleIcon.setImageTintList( 438 ColorStateList.valueOf(mController.getColorItemContent())); 439 mIconAreaLayout.setBackgroundTintList( 440 ColorStateList.valueOf(mController.getColorItemBackground())); 441 mContainerLayout.setOnClickListener(mController::launchBluetoothPairing); 442 } 443 } 444 onGroupActionTriggered(boolean isChecked, MediaDevice device)445 private void onGroupActionTriggered(boolean isChecked, MediaDevice device) { 446 disableSeekBar(); 447 if (isChecked && isDeviceIncluded(mController.getSelectableMediaDevice(), device)) { 448 mController.addDeviceToPlayMedia(device); 449 } else if (!isChecked && isDeviceIncluded(mController.getDeselectableMediaDevice(), 450 device)) { 451 mController.removeDeviceFromPlayMedia(device); 452 } 453 } 454 onItemClick(View view, MediaDevice device)455 private void onItemClick(View view, MediaDevice device) { 456 if (mController.isCurrentOutputDeviceHasSessionOngoing()) { 457 showCustomEndSessionDialog(device); 458 } else { 459 transferOutput(device); 460 } 461 } 462 transferOutput(MediaDevice device)463 private void transferOutput(MediaDevice device) { 464 if (mController.isAnyDeviceTransferring()) { 465 return; 466 } 467 if (isCurrentlyConnected(device)) { 468 Log.d(TAG, "This device is already connected! : " + device.getName()); 469 return; 470 } 471 mController.setTemporaryAllowListExceptionIfNeeded(device); 472 mCurrentActivePosition = -1; 473 mController.connectDevice(device); 474 device.setState(MediaDeviceState.STATE_CONNECTING); 475 notifyDataSetChanged(); 476 } 477 478 @VisibleForTesting showCustomEndSessionDialog(MediaDevice device)479 void showCustomEndSessionDialog(MediaDevice device) { 480 MediaSessionReleaseDialog mediaSessionReleaseDialog = new MediaSessionReleaseDialog( 481 mContext, () -> transferOutput(device), mController.getColorButtonBackground(), 482 mController.getColorItemContent()); 483 mediaSessionReleaseDialog.show(); 484 } 485 cancelMuteAwaitConnection()486 private void cancelMuteAwaitConnection() { 487 mController.cancelMuteAwaitConnection(); 488 notifyDataSetChanged(); 489 } 490 disableFocusPropertyForView(View view)491 private void disableFocusPropertyForView(View view) { 492 view.setFocusable(false); 493 view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 494 } 495 enableFocusPropertyForView(View view)496 private void enableFocusPropertyForView(View view) { 497 view.setFocusable(true); 498 view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); 499 } 500 setUpContentDescriptionForView(View view, MediaDevice device)501 private void setUpContentDescriptionForView(View view, MediaDevice device) { 502 view.setContentDescription( 503 mContext.getString(device.getDeviceType() 504 == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE 505 ? R.string.accessibility_bluetooth_name 506 : R.string.accessibility_cast_name, device.getName())); 507 view.setAccessibilityDelegate(new View.AccessibilityDelegate() { 508 public void onInitializeAccessibilityNodeInfo(View host, 509 AccessibilityNodeInfo info) { 510 super.onInitializeAccessibilityNodeInfo(host, info); 511 host.setOnClickListener(null); 512 } 513 }); 514 } 515 } 516 517 class MediaGroupDividerViewHolder extends RecyclerView.ViewHolder { 518 final TextView mTitleText; 519 MediaGroupDividerViewHolder(@onNull View itemView)520 MediaGroupDividerViewHolder(@NonNull View itemView) { 521 super(itemView); 522 mTitleText = itemView.requireViewById(R.id.title); 523 } 524 onBind(String groupDividerTitle)525 void onBind(String groupDividerTitle) { 526 mTitleText.setTextColor(mController.getColorItemContent()); 527 mTitleText.setText(groupDividerTitle); 528 } 529 } 530 531 @RequiresApi(34) 532 private static class Api34Impl { 533 @DoNotInline getClickListenerBasedOnSelectionBehavior(MediaDevice device, MediaOutputController controller, View.OnClickListener defaultTransferListener)534 static View.OnClickListener getClickListenerBasedOnSelectionBehavior(MediaDevice device, 535 MediaOutputController controller, View.OnClickListener defaultTransferListener) { 536 switch (device.getSelectionBehavior()) { 537 case SELECTION_BEHAVIOR_NONE: 538 return null; 539 case SELECTION_BEHAVIOR_TRANSFER: 540 return defaultTransferListener; 541 case SELECTION_BEHAVIOR_GO_TO_APP: 542 return v -> controller.tryToLaunchInAppRoutingIntent(device.getId(), v); 543 } 544 return defaultTransferListener; 545 } 546 547 @DoNotInline getDeviceStatusIconBasedOnSelectionBehavior(MediaDevice device, Context context)548 static Drawable getDeviceStatusIconBasedOnSelectionBehavior(MediaDevice device, 549 Context context) { 550 switch (device.getSelectionBehavior()) { 551 case SELECTION_BEHAVIOR_NONE: 552 return context.getDrawable(R.drawable.media_output_status_failed); 553 case SELECTION_BEHAVIOR_TRANSFER: 554 return null; 555 case SELECTION_BEHAVIOR_GO_TO_APP: 556 return context.getDrawable(R.drawable.media_output_status_help); 557 } 558 return null; 559 } 560 } 561 } 562