diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFactory.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFactory.java index 2ee286d4b0e..b16bceb7c10 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFactory.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFactory.java @@ -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. * diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java index 1bf54926d3c..c121f550687 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java @@ -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 deviceItems, + @Nullable BluetoothLeBroadcastMetadata metadata, @NonNull DialogEventListener listener, @NonNull Pair[] 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( diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java index 7ca3256b849..43c49a082dc 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java @@ -727,10 +727,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() { diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java index 3c362c00d80..656694d1b2b 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsQrCodeFragment.java @@ -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 getQrCodeBitmap(@Nullable BluetoothLeBroadcastMetadata metadata) { + /** Gets an optional bitmap from metadata. */ + public static Optional 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) { diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java index dec85e47b80..b4c0a2047be 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragmentTest.java @@ -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 TEST_EVENT_DATA = Pair.create(1, 1); private static final Pair[] 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 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 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() { diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java index 7851b2b38f8..d36082c61af 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java @@ -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; @@ -552,6 +553,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()); @@ -565,8 +572,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)