1 /* 2 * Copyright (C) 2022 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.settings.bluetooth; 18 19 import android.bluetooth.BluetoothLeAudioContentMetadata; 20 import android.bluetooth.BluetoothLeBroadcastMetadata; 21 import android.bluetooth.BluetoothLeBroadcastReceiveState; 22 import android.bluetooth.BluetoothLeBroadcastSubgroup; 23 import android.content.Context; 24 import android.graphics.drawable.Drawable; 25 import android.text.TextUtils; 26 import android.view.View; 27 import android.widget.ImageButton; 28 import android.widget.ImageView; 29 30 import androidx.annotation.NonNull; 31 import androidx.preference.Preference; 32 import androidx.preference.PreferenceViewHolder; 33 34 import com.android.settings.R; 35 import com.android.settingslib.Utils; 36 37 import java.util.List; 38 39 /** 40 * Preference to display a broadcast source in the Broadcast Source List. 41 */ 42 class BluetoothBroadcastSourcePreference extends Preference { 43 44 private static final int RESOURCE_ID_UNKNOWN_PROGRAM_INFO = R.string.device_info_default; 45 private static final int RESOURCE_ID_ICON = 46 com.android.settingslib.R.drawable.settings_input_antenna; 47 48 private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata; 49 private BluetoothLeBroadcastReceiveState mBluetoothLeBroadcastReceiveState; 50 private ImageView mFrictionImageView; 51 private String mTitle; 52 private boolean mStatus; 53 private boolean mIsEncrypted; 54 BluetoothBroadcastSourcePreference(@onNull Context context)55 BluetoothBroadcastSourcePreference(@NonNull Context context) { 56 super(context); 57 initUi(); 58 } 59 60 @Override onBindViewHolder(final PreferenceViewHolder view)61 public void onBindViewHolder(final PreferenceViewHolder view) { 62 super.onBindViewHolder(view); 63 view.findViewById(com.android.settingslib.widget.preference.twotarget.R.id.two_target_divider) 64 .setVisibility(View.INVISIBLE); 65 final ImageButton imageButton = 66 (ImageButton) view.findViewById(com.android.settingslib.R.id.icon_button); 67 imageButton.setVisibility(View.GONE); 68 mFrictionImageView = 69 (ImageView) view.findViewById(com.android.settingslib.R.id.friction_icon); 70 updateStatusButton(); 71 } 72 initUi()73 private void initUi() { 74 setLayoutResource(com.android.settingslib.R.layout.preference_access_point); 75 setWidgetLayoutResource(com.android.settingslib.R.layout.access_point_friction_widget); 76 mTitle = getContext().getString(RESOURCE_ID_UNKNOWN_PROGRAM_INFO); 77 mStatus = false; 78 final Drawable drawable = getContext().getDrawable(RESOURCE_ID_ICON); 79 if (drawable != null) { 80 drawable.setTint(Utils.getColorAttrDefaultColor(getContext(), 81 android.R.attr.colorControlNormal)); 82 setIcon(drawable); 83 } 84 } 85 updateStatusButton()86 private void updateStatusButton() { 87 if (mFrictionImageView == null) { 88 return; 89 } 90 if (mStatus || mIsEncrypted) { 91 Drawable drawable; 92 if (mStatus) { 93 drawable = getContext().getDrawable(R.drawable.bluetooth_broadcast_dialog_done); 94 } else { 95 drawable = getContext().getDrawable(R.drawable.ic_friction_lock_closed); 96 } 97 if (drawable != null) { 98 drawable.setTint(Utils.getColorAttrDefaultColor(getContext(), 99 android.R.attr.colorControlNormal)); 100 mFrictionImageView.setImageDrawable(drawable); 101 } 102 mFrictionImageView.setVisibility(View.VISIBLE); 103 } else { 104 mFrictionImageView.setVisibility(View.GONE); 105 } 106 } 107 108 /** 109 * Updates the title and status from BluetoothLeBroadcastMetadata. 110 */ updateMetadataAndRefreshUi(BluetoothLeBroadcastMetadata source, boolean status)111 public void updateMetadataAndRefreshUi(BluetoothLeBroadcastMetadata source, boolean status) { 112 mBluetoothLeBroadcastMetadata = source; 113 mTitle = getProgramInfo(); 114 mIsEncrypted = mBluetoothLeBroadcastMetadata.isEncrypted(); 115 mStatus = status || mBluetoothLeBroadcastReceiveState != null; 116 117 refresh(); 118 } 119 120 /** 121 * Updates the title and status from BluetoothLeBroadcastReceiveState. 122 */ updateReceiveStateAndRefreshUi(BluetoothLeBroadcastReceiveState receiveState)123 public void updateReceiveStateAndRefreshUi(BluetoothLeBroadcastReceiveState receiveState) { 124 mBluetoothLeBroadcastReceiveState = receiveState; 125 mTitle = getProgramInfo(); 126 mStatus = true; 127 128 refresh(); 129 } 130 131 /** 132 * Gets the BluetoothLeBroadcastMetadata. 133 */ getBluetoothLeBroadcastMetadata()134 public BluetoothLeBroadcastMetadata getBluetoothLeBroadcastMetadata() { 135 return mBluetoothLeBroadcastMetadata; 136 } 137 refresh()138 private void refresh() { 139 setTitle(mTitle); 140 updateStatusButton(); 141 } 142 getProgramInfo()143 private String getProgramInfo() { 144 if (mBluetoothLeBroadcastReceiveState != null) { 145 List<BluetoothLeAudioContentMetadata> bluetoothLeAudioContentMetadata = 146 mBluetoothLeBroadcastReceiveState.getSubgroupMetadata(); 147 if (!bluetoothLeAudioContentMetadata.isEmpty()) { 148 return bluetoothLeAudioContentMetadata.stream() 149 .map(i -> i.getProgramInfo()) 150 .findFirst().orElse( 151 getContext().getString(RESOURCE_ID_UNKNOWN_PROGRAM_INFO)); 152 } 153 } 154 if (mBluetoothLeBroadcastMetadata == null) { 155 return getContext().getString(RESOURCE_ID_UNKNOWN_PROGRAM_INFO); 156 } 157 final List<BluetoothLeBroadcastSubgroup> subgroups = 158 mBluetoothLeBroadcastMetadata.getSubgroups(); 159 if (subgroups.isEmpty()) { 160 return getContext().getString(RESOURCE_ID_UNKNOWN_PROGRAM_INFO); 161 } 162 return subgroups.stream() 163 .map(i -> i.getContentMetadata().getProgramInfo()) 164 .filter(i -> !TextUtils.isEmpty(i)) 165 .findFirst().orElse(getContext().getString(RESOURCE_ID_UNKNOWN_PROGRAM_INFO)); 166 } 167 168 /** 169 * Whether the broadcast source is encrypted or not. 170 * @return If true, the broadcast source needs the broadcast code. If false, the broadcast 171 * source does not need the broadcast code. 172 */ isEncrypted()173 public boolean isEncrypted() { 174 return mIsEncrypted; 175 } 176 177 /** 178 * Whether the broadcast source is connected at the beginging. We will get the 179 * BluetoothLeBroadcastReceiveState from the broadcast source. 180 * See {@link BluetoothFindBroadcastsFragment#addConnectedSourcePreference} 181 * @return If true, the broadcast source is already connected by the broadcast sink. 182 */ isCreatedByReceiveState()183 public boolean isCreatedByReceiveState() { 184 return mBluetoothLeBroadcastReceiveState != null; 185 } 186 187 /** 188 * Clear the BluetoothLeBroadcastReceiveState and reset the state when the user clicks the 189 * "leave broadcast" button. 190 */ clearReceiveState()191 public void clearReceiveState() { 192 mBluetoothLeBroadcastReceiveState = null; 193 mTitle = getProgramInfo(); 194 mStatus = false; 195 refresh(); 196 } 197 } 198