Merge "Show qr code in "Share your audio" dialog" into main

This commit is contained in:
Chelsea Hao
2025-01-22 23:35:39 -08:00
committed by Android (Google) Code Review
6 changed files with 128 additions and 26 deletions

View File

@@ -18,6 +18,7 @@ package com.android.settings.connecteddevice.audiosharing;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
@@ -147,7 +148,7 @@ public class AudioSharingDialogFactory {
/**
* Sets the custom image of the dialog custom body.
*
* @param iconRes The text to be used for the title.
* @param iconRes The iconRes to be used for the image.
* @return This builder.
*/
@NonNull
@@ -158,6 +159,20 @@ public class AudioSharingDialogFactory {
return this;
}
/**
* Sets the custom image of the dialog custom body.
*
* @param bitmap The bitmap to be used for the image.
* @return This builder.
*/
@NonNull
public AudioSharingDialogFactory.DialogBuilder setCustomImage(Bitmap bitmap) {
ImageView image = mCustomBody.findViewById(R.id.description_image);
image.setImageBitmap(bitmap);
image.setVisibility(View.VISIBLE);
return this;
}
/**
* Sets the custom message of the dialog custom body.
*

View File

@@ -17,10 +17,13 @@
package com.android.settings.connecteddevice.audiosharing;
import static com.android.settings.connecteddevice.audiosharing.AudioSharingDashboardFragment.SHARE_THEN_PAIR_REQUEST_CODE;
import static com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamsQrCodeFragment.getQrCodeBitmap;
import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast.EXTRA_PAIR_AND_JOIN_SHARING;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;
@@ -48,6 +51,7 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
private static final String TAG = "AudioSharingDialog";
private static final String BUNDLE_KEY_DEVICE_ITEMS = "bundle_key_device_items";
private static final String BUNDLE_KEY_BROADCAST_METADATA = "bundle_key_broadcast_metadata";
// The host creates an instance of this dialog fragment must implement this interface to receive
// event callbacks.
@@ -80,12 +84,14 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
*
* @param host The Fragment this dialog will be hosted.
* @param deviceItems The connected device items eligible for audio sharing.
* @param metadata The audio sharing metadata, nullable.
* @param listener The callback to handle the user action on this dialog.
* @param eventData The eventData to log with for dialog onClick events.
*/
public static void show(
@Nullable Fragment host,
@NonNull List<AudioSharingDeviceItem> deviceItems,
@Nullable BluetoothLeBroadcastMetadata metadata,
@NonNull DialogEventListener listener,
@NonNull Pair<Integer, Object>[] eventData) {
if (host == null) {
@@ -116,6 +122,9 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
Log.d(TAG, "Show up the dialog.");
final Bundle bundle = new Bundle();
bundle.putParcelableList(BUNDLE_KEY_DEVICE_ITEMS, deviceItems);
if (metadata != null) {
bundle.putParcelable(BUNDLE_KEY_BROADCAST_METADATA, metadata);
}
AudioSharingDialogFragment dialogFrag = new AudioSharingDialogFragment();
dialogFrag.setArguments(bundle);
dialogFrag.show(manager, TAG);
@@ -150,7 +159,6 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
}
if (deviceItems.isEmpty()) {
builder.setTitle(R.string.audio_sharing_share_dialog_title)
.setCustomImage(R.drawable.audio_sharing_guidance)
.setCustomMessage(R.string.audio_sharing_dialog_connect_device_content)
.setCustomPositiveButton(
R.string.audio_sharing_pair_button_label,
@@ -172,17 +180,29 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
launcher.setResultListener(sHost, SHARE_THEN_PAIR_REQUEST_CODE);
}
launcher.launch();
})
.setCustomNegativeButton(
R.string.audio_sharing_qrcode_button_label,
v -> {
onCancelClick();
new SubSettingLauncher(getContext())
.setTitleRes(R.string.audio_streams_qr_code_page_title)
.setDestination(AudioStreamsQrCodeFragment.class.getName())
.setSourceMetricsCategory(getMetricsCategory())
.launch();
});
BluetoothLeBroadcastMetadata metadata = arguments.getParcelable(
BUNDLE_KEY_BROADCAST_METADATA, BluetoothLeBroadcastMetadata.class);
Bitmap qrCodeBitmap = metadata == null ? null : getQrCodeBitmap(metadata,
getContext()).orElse(null);
if (qrCodeBitmap != null) {
builder.setCustomImage(qrCodeBitmap)
.setCustomNegativeButton(com.android.settings.R.string.cancel,
v -> onCancelClick());
} else {
builder.setCustomImage(R.drawable.audio_sharing_guidance)
.setCustomNegativeButton(
R.string.audio_sharing_qrcode_button_label,
v -> {
onCancelClick();
new SubSettingLauncher(getContext())
.setTitleRes(R.string.audio_streams_qr_code_page_title)
.setDestination(
AudioStreamsQrCodeFragment.class.getName())
.setSourceMetricsCategory(getMetricsCategory())
.launch();
});
}
} else if (deviceItems.size() == 1) {
AudioSharingDeviceItem deviceItem = Iterables.getOnlyElement(deviceItems);
builder.setTitle(

View File

@@ -723,10 +723,16 @@ public class AudioSharingSwitchBarController extends BasePreferenceController
cleanUpStatesForStartSharing();
}
};
BluetoothLeBroadcastMetadata metadata = mBroadcast == null ? null
: mBroadcast.getLatestBluetoothLeBroadcastMetadata();
AudioSharingUtils.postOnMainThread(
mContext,
() -> AudioSharingDialogFragment.show(
mFragment, mDeviceItemsForSharing, listener, eventData));
mFragment,
mDeviceItemsForSharing,
metadata,
listener,
eventData));
}
private void showErrorDialog() {

View File

@@ -18,6 +18,7 @@ package com.android.settings.connecteddevice.audiosharing.audiostreams;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.Log;
@@ -69,7 +70,7 @@ public class AudioStreamsQrCodeFragment extends InstrumentedFragment {
if (broadcastMetadata == null) {
return;
}
Bitmap bm = getQrCodeBitmap(broadcastMetadata).orElse(null);
Bitmap bm = getQrCodeBitmap(broadcastMetadata, getActivity()).orElse(null);
if (bm == null) {
return;
}
@@ -100,7 +101,9 @@ public class AudioStreamsQrCodeFragment extends InstrumentedFragment {
});
}
private Optional<Bitmap> getQrCodeBitmap(@Nullable BluetoothLeBroadcastMetadata metadata) {
/** Gets an optional bitmap from metadata. */
public static Optional<Bitmap> getQrCodeBitmap(@Nullable BluetoothLeBroadcastMetadata metadata,
Context context) {
if (metadata == null) {
Log.d(TAG, "getQrCodeBitmap: broadcastMetadata is empty!");
return Optional.empty();
@@ -113,7 +116,7 @@ public class AudioStreamsQrCodeFragment extends InstrumentedFragment {
Log.d(TAG, "getQrCodeBitmap: metadata : " + metadata);
try {
int qrcodeSize =
getResources().getDimensionPixelSize(R.dimen.audio_streams_qrcode_size);
context.getResources().getDimensionPixelSize(R.dimen.audio_streams_qrcode_size);
Bitmap bitmap = QrCodeGenerator.encodeQrCode(metadataStr, qrcodeSize);
return Optional.of(bitmap);
} catch (WriterException e) {

View File

@@ -25,6 +25,7 @@ import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothStatusCodes;
import android.content.Context;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -44,6 +45,7 @@ import com.android.settings.R;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowAlertDialogCompat;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
import com.android.settingslib.flags.Flags;
import org.junit.After;
@@ -87,6 +89,11 @@ public class AudioSharingDialogFragmentTest {
private static final Pair<Integer, Object> TEST_EVENT_DATA = Pair.create(1, 1);
private static final Pair<Integer, Object>[] TEST_EVENT_DATA_LIST =
new Pair[] {TEST_EVENT_DATA};
private static final String METADATA_STR =
"BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;"
+ "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;";
private static final BluetoothLeBroadcastMetadata METADATA =
BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(METADATA_STR);
private Fragment mParent;
private FakeFeatureFactory mFeatureFactory;
@@ -123,7 +130,7 @@ public class AudioSharingDialogFragmentTest {
public void onCreateDialog_flagOff_dialogNotExist() {
mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
AudioSharingDialogFragment.show(
mParent, new ArrayList<>(), EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
mParent, new ArrayList<>(), null, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNull();
@@ -133,17 +140,18 @@ public class AudioSharingDialogFragmentTest {
public void onCreateDialog_unattachedFragment_dialogNotExist() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
AudioSharingDialogFragment.show(
new Fragment(), new ArrayList<>(), EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
new Fragment(), new ArrayList<>(), null, EMPTY_EVENT_LISTENER,
TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNull();
}
@Test
public void onCreateDialog_flagOn_noExtraConnectedDevice() {
public void onCreateDialog_flagOn_qrCodeBitmapNull_noExtraConnectedDevice() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
AudioSharingDialogFragment.show(
mParent, new ArrayList<>(), EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
mParent, new ArrayList<>(), null, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -176,6 +184,7 @@ public class AudioSharingDialogFragmentTest {
AudioSharingDialogFragment.show(
mParent,
new ArrayList<>(),
null,
new AudioSharingDialogFragment.DialogEventListener() {
@Override
public void onPositiveClick() {
@@ -201,12 +210,13 @@ public class AudioSharingDialogFragmentTest {
}
@Test
public void onCreateDialog_noExtraConnectedDevice_showQRCode() {
public void onCreateDialog_noExtraConnectedDevice_showQRCodeButton() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
AtomicBoolean isQrCodeBtnClicked = new AtomicBoolean(false);
AudioSharingDialogFragment.show(
mParent,
new ArrayList<>(),
null,
new AudioSharingDialogFragment.DialogEventListener() {
@Override
public void onCancelClick() {
@@ -219,6 +229,8 @@ public class AudioSharingDialogFragmentTest {
assertThat(dialog).isNotNull();
Button qrCodeBtn = dialog.findViewById(R.id.negative_btn);
assertThat(qrCodeBtn).isNotNull();
assertThat(qrCodeBtn.getText().toString())
.isEqualTo(mParent.getString(R.string.audio_sharing_qrcode_button_label));
qrCodeBtn.performClick();
shadowMainLooper().idle();
@@ -231,12 +243,47 @@ public class AudioSharingDialogFragmentTest {
assertThat(dialog.isShowing()).isFalse();
}
@Test
public void onCreateDialog_noExtraConnectedDevice_hasMetadata_showCancelButton() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
AtomicBoolean isCancelBtnClicked = new AtomicBoolean(false);
AudioSharingDialogFragment.show(
mParent,
new ArrayList<>(),
METADATA,
new AudioSharingDialogFragment.DialogEventListener() {
@Override
public void onCancelClick() {
isCancelBtnClicked.set(true);
}
},
TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
assertThat(dialog).isNotNull();
ImageView image = dialog.findViewById(R.id.description_image);
assertThat(image).isNotNull();
Button cancelBtn = dialog.findViewById(R.id.negative_btn);
assertThat(cancelBtn).isNotNull();
cancelBtn.performClick();
shadowMainLooper().idle();
verify(mFeatureFactory.metricsFeatureProvider)
.action(
any(Context.class),
eq(SettingsEnums.ACTION_AUDIO_SHARING_DIALOG_NEGATIVE_BTN_CLICKED),
eq(TEST_EVENT_DATA));
assertThat(isCancelBtnClicked.get()).isTrue();
assertThat(dialog.isShowing()).isFalse();
}
@Test
public void onCreateDialog_flagOn_singleExtraConnectedDevice() {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
list.add(TEST_DEVICE_ITEM1);
AudioSharingDialogFragment.show(mParent, list, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
AudioSharingDialogFragment.show(mParent, list, null, EMPTY_EVENT_LISTENER,
TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -273,7 +320,8 @@ public class AudioSharingDialogFragmentTest {
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
ArrayList<AudioSharingDeviceItem> list = new ArrayList<>();
list.add(TEST_DEVICE_ITEM1);
AudioSharingDialogFragment.show(mParent, list, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
AudioSharingDialogFragment.show(mParent, list, null, EMPTY_EVENT_LISTENER,
TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -300,6 +348,7 @@ public class AudioSharingDialogFragmentTest {
AudioSharingDialogFragment.show(
mParent,
list,
null,
new AudioSharingDialogFragment.DialogEventListener() {
@Override
public void onItemClick(@NonNull AudioSharingDeviceItem item) {
@@ -332,7 +381,8 @@ public class AudioSharingDialogFragmentTest {
list.add(TEST_DEVICE_ITEM1);
list.add(TEST_DEVICE_ITEM2);
list.add(TEST_DEVICE_ITEM3);
AudioSharingDialogFragment.show(mParent, list, EMPTY_EVENT_LISTENER, TEST_EVENT_DATA_LIST);
AudioSharingDialogFragment.show(mParent, list, null, EMPTY_EVENT_LISTENER,
TEST_EVENT_DATA_LIST);
shadowMainLooper().idle();
AlertDialog dialog = ShadowAlertDialogCompat.getLatestAlertDialog();
@@ -371,6 +421,7 @@ public class AudioSharingDialogFragmentTest {
AudioSharingDialogFragment.show(
mParent,
list,
null,
new AudioSharingDialogFragment.DialogEventListener() {
@Override
public void onCancelClick() {

View File

@@ -79,6 +79,7 @@ import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
@@ -543,6 +544,12 @@ public class AudioSharingSwitchBarControllerTest {
public void onBroadcastMetadataChanged_singleActiveDevice_showJoinAudioSharingDialog() {
FeatureFlagUtils.setEnabled(
mContext, FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
String metadataStr =
"BLUETOOTH:UUID:184F;BN:VGVzdA==;AT:1;AD:00A1A1A1A1A1;BI:1E240;BC:VGVzdENvZGU=;"
+ "MD:BgNwVGVzdA==;AS:1;PI:A0;NS:1;BS:3;NB:2;SM:BQNUZXN0BARlbmc=;;";
// Use real metadata as the dialog will display a qr code image
BluetoothLeBroadcastMetadata realMetadata =
BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(metadataStr);
when(mBtnView.isEnabled()).thenReturn(true);
when(mAssistant.getAllConnectedDevices()).thenReturn(ImmutableList.of(mDevice2));
when(mAssistant.getAllSources(any(BluetoothDevice.class))).thenReturn(ImmutableList.of());
@@ -556,8 +563,8 @@ public class AudioSharingSwitchBarControllerTest {
AudioSharingProgressDialogFragment.class.getName());
when(mBroadcast.isEnabled(null)).thenReturn(true);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(mMetadata);
mController.mBroadcastCallback.onBroadcastMetadataChanged(/* reason= */ 1, mMetadata);
when(mBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(realMetadata);
mController.mBroadcastCallback.onBroadcastMetadataChanged(/* reason= */ 1, realMetadata);
shadowOf(Looper.getMainLooper()).idle();
verify(mFeatureFactory.metricsFeatureProvider)