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.app.settings.SettingsEnums;
20 import android.bluetooth.BluetoothLeBroadcastMetadata;
21 import android.graphics.Bitmap;
22 import android.os.Bundle;
23 import android.util.Log;
24 import android.view.LayoutInflater;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.widget.ImageView;
28 import android.widget.TextView;
29 
30 import androidx.annotation.NonNull;
31 import androidx.annotation.Nullable;
32 
33 import com.android.settings.R;
34 import com.android.settings.bluetooth.Utils;
35 import com.android.settings.core.InstrumentedFragment;
36 import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
37 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
38 import com.android.settingslib.qrcode.QrCodeGenerator;
39 import com.android.settingslib.utils.ThreadUtils;
40 
41 import com.google.zxing.WriterException;
42 
43 import java.nio.charset.StandardCharsets;
44 import java.util.List;
45 import java.util.Optional;
46 
47 public class AudioStreamsQrCodeFragment extends InstrumentedFragment {
48     private static final String TAG = "AudioStreamsQrCodeFragment";
49 
50     @Override
getMetricsCategory()51     public int getMetricsCategory() {
52         return SettingsEnums.AUDIO_STREAM_QR_CODE;
53     }
54 
55     @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)56     public final View onCreateView(
57             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
58         return inflater.inflate(R.xml.bluetooth_audio_streams_qr_code, container, false);
59     }
60 
61     @Override
onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)62     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
63         super.onViewCreated(view, savedInstanceState);
64         var unused = ThreadUtils.postOnBackgroundThread(
65                 () -> {
66                     BluetoothLeBroadcastMetadata broadcastMetadata = getBroadcastMetadata();
67                     if (broadcastMetadata == null) {
68                         return;
69                     }
70                     Bitmap bm = getQrCodeBitmap(broadcastMetadata).orElse(null);
71                     if (bm == null) {
72                         return;
73                     }
74 
75                     ThreadUtils.postOnMainThread(
76                             () -> {
77                                 ((ImageView) view.requireViewById(R.id.qrcode_view))
78                                         .setImageBitmap(bm);
79                                 if (broadcastMetadata.getBroadcastCode() != null) {
80                                     String password =
81                                             new String(
82                                                     broadcastMetadata.getBroadcastCode(),
83                                                     StandardCharsets.UTF_8);
84                                     String passwordText =
85                                             getString(
86                                                     R.string.audio_streams_qr_code_page_password,
87                                                     password);
88                                     ((TextView) view.requireViewById(R.id.password))
89                                             .setText(passwordText);
90                                 }
91                                 TextView summaryView = view.requireViewById(android.R.id.summary);
92                                 String summary =
93                                         getString(
94                                                 R.string.audio_streams_qr_code_page_description,
95                                                 broadcastMetadata.getBroadcastName());
96                                 summaryView.setText(summary);
97                             });
98                 });
99     }
100 
getQrCodeBitmap(@ullable BluetoothLeBroadcastMetadata metadata)101     private Optional<Bitmap> getQrCodeBitmap(@Nullable BluetoothLeBroadcastMetadata metadata) {
102         if (metadata == null) {
103             Log.d(TAG, "getQrCodeBitmap: broadcastMetadata is empty!");
104             return Optional.empty();
105         }
106         String metadataStr = BluetoothLeBroadcastMetadataExt.INSTANCE.toQrCodeString(metadata);
107         if (metadataStr.isEmpty()) {
108             Log.d(TAG, "getQrCodeBitmap: metadataStr is empty!");
109             return Optional.empty();
110         }
111         Log.d(TAG, "getQrCodeBitmap: metadata : " + metadata);
112         try {
113             int qrcodeSize =
114                     getResources().getDimensionPixelSize(R.dimen.audio_streams_qrcode_size);
115             Bitmap bitmap = QrCodeGenerator.encodeQrCode(metadataStr, qrcodeSize);
116             return Optional.of(bitmap);
117         } catch (WriterException e) {
118             Log.d(
119                     TAG,
120                     "getQrCodeBitmap: broadcastMetadata "
121                             + metadata
122                             + " qrCode generation exception "
123                             + e);
124         }
125 
126         return Optional.empty();
127     }
128 
129     @Nullable
getBroadcastMetadata()130     private BluetoothLeBroadcastMetadata getBroadcastMetadata() {
131         LocalBluetoothLeBroadcast localBluetoothLeBroadcast =
132                 Utils.getLocalBtManager(getActivity())
133                         .getProfileManager()
134                         .getLeAudioBroadcastProfile();
135         if (localBluetoothLeBroadcast == null) {
136             Log.d(TAG, "getBroadcastMetadataQrCode: localBluetoothLeBroadcast is null!");
137             return null;
138         }
139 
140         List<BluetoothLeBroadcastMetadata> metadata =
141                 localBluetoothLeBroadcast.getAllBroadcastMetadata();
142         if (metadata.isEmpty()) {
143             Log.d(TAG, "getBroadcastMetadataQrCode: metadata is null!");
144             return null;
145         }
146 
147         return metadata.get(0);
148     }
149 }
150