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