1 /*
2  * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams;
18 
19 import android.bluetooth.BluetoothLeBroadcastMetadata;
20 import android.bluetooth.BluetoothLeBroadcastReceiveState;
21 import android.content.Context;
22 import android.util.AttributeSet;
23 import android.view.View;
24 
25 import androidx.annotation.Nullable;
26 import androidx.annotation.VisibleForTesting;
27 import androidx.preference.PreferenceViewHolder;
28 
29 import com.android.settings.R;
30 import com.android.settingslib.utils.ThreadUtils;
31 import com.android.settingslib.widget.TwoTargetPreference;
32 
33 /**
34  * Custom preference class for managing audio stream preferences with an optional lock icon. Extends
35  * {@link TwoTargetPreference}.
36  */
37 class AudioStreamPreference extends TwoTargetPreference {
38     private boolean mIsConnected = false;
39     private boolean mIsEncrypted = true;
40     @Nullable private AudioStream mAudioStream;
41 
42     /**
43      * Update preference UI based on connection status
44      *
45      * @param isConnected Is this stream connected
46      * @param summary Summary text
47      * @param onPreferenceClickListener Click listener for the preference
48      */
setIsConnected( boolean isConnected, String summary, @Nullable OnPreferenceClickListener onPreferenceClickListener)49     void setIsConnected(
50             boolean isConnected,
51             String summary,
52             @Nullable OnPreferenceClickListener onPreferenceClickListener) {
53         if (mIsConnected == isConnected
54                 && getSummary() == summary
55                 && getOnPreferenceClickListener() == onPreferenceClickListener) {
56             // Nothing to update.
57             return;
58         }
59         mIsConnected = isConnected;
60         setSummary(summary);
61         setOnPreferenceClickListener(onPreferenceClickListener);
62         notifyChanged();
63     }
64 
65     @VisibleForTesting
AudioStreamPreference(Context context, @Nullable AttributeSet attrs)66     AudioStreamPreference(Context context, @Nullable AttributeSet attrs) {
67         super(context, attrs);
68         setIcon(com.android.settingslib.R.drawable.ic_bt_le_audio_sharing);
69     }
70 
setAudioStreamState(AudioStreamsProgressCategoryController.AudioStreamState state)71     void setAudioStreamState(AudioStreamsProgressCategoryController.AudioStreamState state) {
72         if (mAudioStream != null) {
73             mAudioStream.setState(state);
74         }
75     }
76 
setAudioStreamMetadata(BluetoothLeBroadcastMetadata metadata)77     void setAudioStreamMetadata(BluetoothLeBroadcastMetadata metadata) {
78         if (mAudioStream != null) {
79             mAudioStream.setMetadata(metadata);
80             // Update title based on the metadata
81             String broadcastName = AudioStreamsHelper.getBroadcastName(metadata);
82             ThreadUtils.postOnMainThread(() -> setTitle(broadcastName));
83         }
84     }
85 
getAudioStreamBroadcastId()86     int getAudioStreamBroadcastId() {
87         return mAudioStream != null ? mAudioStream.getBroadcastId() : -1;
88     }
89 
90     @Nullable
getAudioStreamBroadcastName()91     String getAudioStreamBroadcastName() {
92         return mAudioStream != null ? mAudioStream.getBroadcastName() : null;
93     }
94 
getAudioStreamRssi()95     int getAudioStreamRssi() {
96         return mAudioStream != null ? mAudioStream.getRssi() : -1;
97     }
98 
99     @Nullable
getAudioStreamMetadata()100     BluetoothLeBroadcastMetadata getAudioStreamMetadata() {
101         return mAudioStream != null ? mAudioStream.getMetadata() : null;
102     }
103 
getAudioStreamState()104     AudioStreamsProgressCategoryController.AudioStreamState getAudioStreamState() {
105         return mAudioStream != null
106                 ? mAudioStream.getState()
107                 : AudioStreamsProgressCategoryController.AudioStreamState.UNKNOWN;
108     }
109 
getSourceOriginForLogging()110     SourceOriginForLogging getSourceOriginForLogging() {
111         return mAudioStream != null
112                 ? mAudioStream.getSourceOriginForLogging()
113                 : SourceOriginForLogging.UNKNOWN;
114     }
115 
116     @Override
shouldHideSecondTarget()117     protected boolean shouldHideSecondTarget() {
118         return mIsConnected || !mIsEncrypted;
119     }
120 
121     @Override
getSecondTargetResId()122     protected int getSecondTargetResId() {
123         return R.layout.preference_widget_lock;
124     }
125 
126     @Override
onBindViewHolder(PreferenceViewHolder holder)127     public void onBindViewHolder(PreferenceViewHolder holder) {
128         super.onBindViewHolder(holder);
129         View divider =
130                 holder.findViewById(
131                         com.android.settingslib.widget.preference.twotarget.R.id
132                                 .two_target_divider);
133         if (divider != null) {
134             divider.setVisibility(View.GONE);
135         }
136     }
137 
fromMetadata( Context context, BluetoothLeBroadcastMetadata source, SourceOriginForLogging sourceOriginForLogging)138     static AudioStreamPreference fromMetadata(
139             Context context,
140             BluetoothLeBroadcastMetadata source,
141             SourceOriginForLogging sourceOriginForLogging) {
142         AudioStreamPreference preference = new AudioStreamPreference(context, /* attrs= */ null);
143         preference.setIsEncrypted(source.isEncrypted());
144         preference.setTitle(AudioStreamsHelper.getBroadcastName(source));
145         preference.setAudioStream(new AudioStream(source, sourceOriginForLogging));
146         return preference;
147     }
148 
fromReceiveState( Context context, BluetoothLeBroadcastReceiveState receiveState)149     static AudioStreamPreference fromReceiveState(
150             Context context, BluetoothLeBroadcastReceiveState receiveState) {
151         AudioStreamPreference preference = new AudioStreamPreference(context, /* attrs= */ null);
152         preference.setTitle(AudioStreamsHelper.getBroadcastName(receiveState));
153         preference.setAudioStream(new AudioStream(receiveState));
154         return preference;
155     }
156 
setAudioStream(AudioStream audioStream)157     private void setAudioStream(AudioStream audioStream) {
158         mAudioStream = audioStream;
159     }
160 
setIsEncrypted(boolean isEncrypted)161     private void setIsEncrypted(boolean isEncrypted) {
162         mIsEncrypted = isEncrypted;
163     }
164 
165     private static final class AudioStream {
166         private static final int UNAVAILABLE = -1;
167         @Nullable private BluetoothLeBroadcastMetadata mMetadata;
168         @Nullable private BluetoothLeBroadcastReceiveState mReceiveState;
169         private SourceOriginForLogging mSourceOriginForLogging = SourceOriginForLogging.UNKNOWN;
170         private AudioStreamsProgressCategoryController.AudioStreamState mState =
171                 AudioStreamsProgressCategoryController.AudioStreamState.UNKNOWN;
172 
AudioStream( BluetoothLeBroadcastMetadata metadata, SourceOriginForLogging sourceOriginForLogging)173         private AudioStream(
174                 BluetoothLeBroadcastMetadata metadata,
175                 SourceOriginForLogging sourceOriginForLogging) {
176             mMetadata = metadata;
177             mSourceOriginForLogging = sourceOriginForLogging;
178         }
179 
AudioStream(BluetoothLeBroadcastReceiveState receiveState)180         private AudioStream(BluetoothLeBroadcastReceiveState receiveState) {
181             mReceiveState = receiveState;
182         }
183 
getBroadcastId()184         private int getBroadcastId() {
185             return mMetadata != null
186                     ? mMetadata.getBroadcastId()
187                     : mReceiveState != null ? mReceiveState.getBroadcastId() : UNAVAILABLE;
188         }
189 
getBroadcastName()190         private @Nullable String getBroadcastName() {
191             return mMetadata != null
192                     ? AudioStreamsHelper.getBroadcastName(mMetadata)
193                     : mReceiveState != null
194                             ? AudioStreamsHelper.getBroadcastName(mReceiveState)
195                             : null;
196         }
197 
getRssi()198         private int getRssi() {
199             return mMetadata != null ? mMetadata.getRssi() : Integer.MAX_VALUE;
200         }
201 
getState()202         private AudioStreamsProgressCategoryController.AudioStreamState getState() {
203             return mState;
204         }
205 
getSourceOriginForLogging()206         private SourceOriginForLogging getSourceOriginForLogging() {
207             return mSourceOriginForLogging;
208         }
209 
210         @Nullable
getMetadata()211         private BluetoothLeBroadcastMetadata getMetadata() {
212             return mMetadata;
213         }
214 
setState(AudioStreamsProgressCategoryController.AudioStreamState state)215         private void setState(AudioStreamsProgressCategoryController.AudioStreamState state) {
216             mState = state;
217         }
218 
setMetadata(BluetoothLeBroadcastMetadata metadata)219         private void setMetadata(BluetoothLeBroadcastMetadata metadata) {
220             mMetadata = metadata;
221         }
222     }
223 }
224