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.content.Context; 22 import android.content.res.Resources; 23 import android.graphics.Bitmap; 24 import android.graphics.drawable.Drawable; 25 import android.os.Bundle; 26 import android.util.Log; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.widget.ImageView; 31 import android.widget.TextView; 32 33 import androidx.annotation.NonNull; 34 import androidx.annotation.Nullable; 35 import androidx.core.graphics.drawable.RoundedBitmapDrawable; 36 import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; 37 38 import com.android.settings.R; 39 import com.android.settings.bluetooth.Utils; 40 import com.android.settings.connecteddevice.audiosharing.AudioSharingFeatureProvider; 41 import com.android.settings.core.InstrumentedFragment; 42 import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt; 43 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; 44 import com.android.settingslib.qrcode.QrCodeGenerator; 45 import com.android.settingslib.utils.ThreadUtils; 46 import com.android.settings.overlay.FeatureFactory; 47 48 import com.google.zxing.WriterException; 49 50 import java.nio.charset.StandardCharsets; 51 import java.util.List; 52 import java.util.Optional; 53 54 public class AudioStreamsQrCodeFragment extends InstrumentedFragment { 55 private static final String TAG = "AudioStreamsQrCodeFragment"; 56 57 AudioSharingFeatureProvider audioSharingFeatureProvider = 58 FeatureFactory.getFeatureFactory().getAudioSharingFeatureProvider(); 59 60 @Override getMetricsCategory()61 public int getMetricsCategory() { 62 return SettingsEnums.AUDIO_STREAM_QR_CODE; 63 } 64 65 @Override onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)66 public final View onCreateView( 67 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 68 return inflater.inflate(R.layout.bluetooth_audio_streams_qr_code, container, false); 69 } 70 71 @Override onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)72 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 73 super.onViewCreated(view, savedInstanceState); 74 // Collapse or expand the app bar based on orientation for better display the qr code image. 75 AudioStreamsHelper.configureAppBarByOrientation(getActivity()); 76 var unused = 77 ThreadUtils.postOnBackgroundThread( 78 () -> { 79 BluetoothLeBroadcastMetadata broadcastMetadata = getBroadcastMetadata(); 80 if (broadcastMetadata == null) { 81 return; 82 } 83 Drawable drawable = 84 getQrCodeDrawable(broadcastMetadata, getActivity()) 85 .orElse(null); 86 if (drawable == null) { 87 return; 88 } 89 90 ThreadUtils.postOnMainThread( 91 () -> { 92 audioSharingFeatureProvider.setQrCode( 93 this, 94 view, 95 R.id.qrcode_view, 96 drawable, 97 BluetoothLeBroadcastMetadataExt.INSTANCE 98 .toQrCodeString(broadcastMetadata)); 99 if (broadcastMetadata.getBroadcastCode() != null) { 100 String password = 101 new String( 102 broadcastMetadata.getBroadcastCode(), 103 StandardCharsets.UTF_8); 104 String passwordText = 105 getString( 106 R.string 107 .audio_streams_qr_code_page_password, 108 password); 109 ((TextView) view.requireViewById(R.id.password)) 110 .setText(passwordText); 111 } 112 TextView summaryView = 113 view.requireViewById(android.R.id.summary); 114 String summary = 115 getString( 116 R.string 117 .audio_streams_qr_code_page_description, 118 broadcastMetadata.getBroadcastName()); 119 summaryView.setText(summary); 120 }); 121 }); 122 } 123 124 /** Gets an optional drawable from metadata. */ getQrCodeDrawable( @ullable BluetoothLeBroadcastMetadata metadata, Context context)125 public static Optional<Drawable> getQrCodeDrawable( 126 @Nullable BluetoothLeBroadcastMetadata metadata, 127 Context context) { 128 if (metadata == null) { 129 Log.d(TAG, "getQrCodeDrawable: broadcastMetadata is empty!"); 130 return Optional.empty(); 131 } 132 String metadataStr = BluetoothLeBroadcastMetadataExt.INSTANCE.toQrCodeString(metadata); 133 if (metadataStr.isEmpty()) { 134 Log.d(TAG, "getQrCodeDrawable: metadataStr is empty!"); 135 return Optional.empty(); 136 } 137 Log.d(TAG, "getQrCodeDrawable: metadata : " + metadata); 138 try { 139 Resources resources = context.getResources(); 140 int qrcodeSize = resources.getDimensionPixelSize(R.dimen.audio_streams_qrcode_size); 141 int margin = resources.getDimensionPixelSize(R.dimen.audio_streams_qrcode_margin); 142 Bitmap bitmap = QrCodeGenerator.encodeQrCode(metadataStr, qrcodeSize, margin); 143 RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(resources, bitmap); 144 drawable.setCornerRadius(resources.getDimensionPixelSize( 145 R.dimen.audio_streams_qrcode_preview_radius)); 146 return Optional.of(drawable); 147 } catch (WriterException e) { 148 Log.d( 149 TAG, 150 "getQrCodeDrawable: broadcastMetadata " 151 + metadata 152 + " qrCode generation exception " 153 + e); 154 } 155 156 return Optional.empty(); 157 } 158 159 @Nullable getBroadcastMetadata()160 private BluetoothLeBroadcastMetadata getBroadcastMetadata() { 161 LocalBluetoothLeBroadcast localBluetoothLeBroadcast = 162 Utils.getLocalBtManager(getActivity()) 163 .getProfileManager() 164 .getLeAudioBroadcastProfile(); 165 if (localBluetoothLeBroadcast == null) { 166 Log.d(TAG, "getBroadcastMetadataQrCode: localBluetoothLeBroadcast is null!"); 167 return null; 168 } 169 170 List<BluetoothLeBroadcastMetadata> metadata = 171 localBluetoothLeBroadcast.getAllBroadcastMetadata(); 172 if (metadata.isEmpty()) { 173 Log.d(TAG, "getBroadcastMetadataQrCode: metadata is null!"); 174 return null; 175 } 176 177 return metadata.get(0); 178 } 179 } 180