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