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