diff --git a/src/com/android/settings/media/MediaOutputIndicatorSlice.java b/src/com/android/settings/media/MediaOutputIndicatorSlice.java index 860a933ee4e..eb4987953d1 100644 --- a/src/com/android/settings/media/MediaOutputIndicatorSlice.java +++ b/src/com/android/settings/media/MediaOutputIndicatorSlice.java @@ -129,9 +129,9 @@ public class MediaOutputIndicatorSlice implements CustomSliceable { // Return true if // 1. AudioMode is not in on-going call // 2. worker is not null - // 3. Available devices are more than 1 + // 3. Available devices are more than 0 return getWorker() != null && !com.android.settingslib.Utils.isAudioModeOngoingCall(mContext) - && getWorker().getMediaDevices().size() > 1; + && getWorker().getMediaDevices().size() > 0; } } diff --git a/src/com/android/settings/media/MediaOutputSlice.java b/src/com/android/settings/media/MediaOutputSlice.java index 785d1df38e3..9dbf948ec99 100644 --- a/src/com/android/settings/media/MediaOutputSlice.java +++ b/src/com/android/settings/media/MediaOutputSlice.java @@ -22,8 +22,11 @@ import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE; import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_SLICE_URI; import android.app.PendingIntent; +import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.net.Uri; import android.text.SpannableString; @@ -39,6 +42,8 @@ import androidx.slice.builders.SliceAction; import com.android.settings.R; import com.android.settings.Utils; +import com.android.settings.bluetooth.BluetoothPairingDetail; +import com.android.settings.core.SubSettingLauncher; import com.android.settings.slices.CustomSliceable; import com.android.settings.slices.SliceBackgroundWorker; import com.android.settings.slices.SliceBroadcastReceiver; @@ -81,7 +86,6 @@ public class MediaOutputSlice implements CustomSliceable { final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY) .setAccentColor(COLOR_NOT_TINTED); - if (!isVisible()) { Log.d(TAG, "getSlice() is not visible"); return listBuilder.build(); @@ -99,24 +103,53 @@ public class MediaOutputSlice implements CustomSliceable { } } else { final MediaDevice connectedDevice = worker.getCurrentConnectedMediaDevice(); - final boolean isTouched = worker.getIsTouched(); - // Fix the last top device when user press device to transfer. - final MediaDevice topDevice = isTouched ? worker.getTopDevice() : connectedDevice; + if (devices.size() == 1) { + // Zero state + addRow(connectedDevice, connectedDevice, listBuilder); + listBuilder.addRow(getPairNewRow()); + } else { + final boolean isTouched = worker.getIsTouched(); + // Fix the last top device when user press device to transfer. + final MediaDevice topDevice = isTouched ? worker.getTopDevice() : connectedDevice; - if (topDevice != null) { - addRow(topDevice, connectedDevice, listBuilder); - worker.setTopDevice(topDevice); - } + if (topDevice != null) { + addRow(topDevice, connectedDevice, listBuilder); + worker.setTopDevice(topDevice); + } - for (MediaDevice device : devices) { - if (topDevice == null || !TextUtils.equals(topDevice.getId(), device.getId())) { - addRow(device, connectedDevice, listBuilder); + for (MediaDevice device : devices) { + if (topDevice == null || !TextUtils.equals(topDevice.getId(), device.getId())) { + addRow(device, connectedDevice, listBuilder); + } } } } return listBuilder.build(); } + private ListBuilder.RowBuilder getPairNewRow() { + final Drawable d = mContext.getDrawable(R.drawable.ic_add_24dp); + d.setColorFilter(new PorterDuffColorFilter(Utils.getColorAccentDefaultColor(mContext), + PorterDuff.Mode.SRC_IN)); + final IconCompat icon = Utils.createIconWithDrawable(d); + final String title = mContext.getString(R.string.bluetooth_pairing_pref_title); + final Intent intent = new SubSettingLauncher(mContext) + .setDestination(BluetoothPairingDetail.class.getName()) + .setTitleRes(R.string.bluetooth_pairing_page_title) + .setSourceMetricsCategory(SettingsEnums.PANEL_MEDIA_OUTPUT) + .toIntent(); + final SliceAction primarySliceAction = SliceAction.createDeeplink( + PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */), + IconCompat.createWithResource(mContext, R.drawable.ic_add_24dp/*ic_add_blue_24dp*/), + ListBuilder.ICON_IMAGE, + mContext.getText(R.string.bluetooth_pairing_pref_title)); + final ListBuilder.RowBuilder builder = new ListBuilder.RowBuilder() + .setTitleItem(icon, ListBuilder.ICON_IMAGE) + .setTitle(title) + .setPrimaryAction(primarySliceAction); + return builder; + } + private ListBuilder.InputRangeBuilder getGroupRow() { final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_speaker_group_black_24dp); @@ -321,10 +354,10 @@ public class MediaOutputSlice implements CustomSliceable { // Return true if // 1. AudioMode is not in on-going call // 2. worker is not null - // 3. Available devices are more than 1 + // 3. Available devices are more than 0 return getWorker() != null && !com.android.settingslib.Utils.isAudioModeOngoingCall(mContext) - && getWorker().getMediaDevices().size() > 1; + && getWorker().getMediaDevices().size() > 0; } } diff --git a/src/com/android/settings/sound/MediaOutputPreferenceController.java b/src/com/android/settings/sound/MediaOutputPreferenceController.java index b133713f567..c731bc3385b 100644 --- a/src/com/android/settings/sound/MediaOutputPreferenceController.java +++ b/src/com/android/settings/sound/MediaOutputPreferenceController.java @@ -23,6 +23,7 @@ import android.media.AudioManager; import android.text.TextUtils; import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settingslib.Utils; @@ -46,6 +47,15 @@ public class MediaOutputPreferenceController extends AudioSwitchPreferenceContro super(context, key); } + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + if (!Utils.isAudioModeOngoingCall(mContext)) { + mPreference.setVisible(true); + } + } + @Override public void updateState(Preference preference) { if (preference == null) { @@ -61,7 +71,6 @@ public class MediaOutputPreferenceController extends AudioSwitchPreferenceContro return; } - boolean deviceConnected = false; BluetoothDevice activeDevice = null; // Show preference if there is connected or previously connected device // Find active device and set its name as the preference's summary @@ -70,10 +79,8 @@ public class MediaOutputPreferenceController extends AudioSwitchPreferenceContro if (mAudioManager.getMode() == AudioManager.MODE_NORMAL && ((connectedA2dpDevices != null && !connectedA2dpDevices.isEmpty()) || (connectedHADevices != null && !connectedHADevices.isEmpty()))) { - deviceConnected = true; activeDevice = findActiveDevice(); } - mPreference.setVisible(deviceConnected); mPreference.setSummary((activeDevice == null) ? mContext.getText(R.string.media_output_default_summary) : activeDevice.getAlias()); diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java index cc4a870fb4e..086beedc1f3 100644 --- a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java +++ b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java @@ -123,7 +123,6 @@ public class MediaOutputIndicatorSliceTest { @Test public void getSlice_withConnectedDevice_verifyMetadata() { mDevices.add(mDevice1); - mDevices.add(mDevice2); when(sMediaOutputIndicatorWorker.getMediaDevices()).thenReturn(mDevices); doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice(); mAudioManager.setMode(AudioManager.MODE_NORMAL); diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputSliceTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputSliceTest.java index 426eaccdfc7..305d1ab72b9 100644 --- a/tests/robotests/src/com/android/settings/media/MediaOutputSliceTest.java +++ b/tests/robotests/src/com/android/settings/media/MediaOutputSliceTest.java @@ -410,6 +410,69 @@ public class MediaOutputSliceTest { R.string.media_output_switch_error_text))).isNotEqualTo(-1); } + @Test + public void getSlice_zeroState_containPairingText() { + final List mSelectedDevices = new ArrayList<>(); + final List mSelectableDevices = new ArrayList<>(); + mDevices.clear(); + final MediaDevice device = mock(MediaDevice.class); + when(device.getName()).thenReturn(TEST_DEVICE_1_NAME); + when(device.getIcon()).thenReturn(mTestDrawable); + when(device.getMaxVolume()).thenReturn(100); + when(device.isConnected()).thenReturn(true); + when(device.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE); + when(device.getId()).thenReturn(TEST_DEVICE_1_ID); + mSelectedDevices.add(device); + when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(device); + mDevices.add(device); + when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mSelectedDevices); + when(mLocalMediaManager.getSelectableMediaDevice()).thenReturn(mSelectableDevices); + mMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices); + + final Slice mediaSlice = mMediaOutputSlice.getSlice(); + String sliceInfo = SliceQuery.findAll(mediaSlice, FORMAT_SLICE, HINT_LIST_ITEM, + null).toString(); + + assertThat(TextUtils.indexOf(sliceInfo, mContext.getText( + R.string.bluetooth_pairing_pref_title))).isNotEqualTo(-1); + } + + @Test + public void getSlice_twoConnectedDevices_notContainPairingText() { + final List mSelectedDevices = new ArrayList<>(); + final List mSelectableDevices = new ArrayList<>(); + mDevices.clear(); + final MediaDevice device = mock(MediaDevice.class); + when(device.getName()).thenReturn(TEST_DEVICE_1_NAME); + when(device.getIcon()).thenReturn(mTestDrawable); + when(device.getMaxVolume()).thenReturn(100); + when(device.isConnected()).thenReturn(true); + when(device.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE); + when(device.getId()).thenReturn(TEST_DEVICE_1_ID); + final MediaDevice device2 = mock(MediaDevice.class); + when(device2.getName()).thenReturn(TEST_DEVICE_2_NAME); + when(device2.getIcon()).thenReturn(mTestDrawable); + when(device2.getMaxVolume()).thenReturn(100); + when(device2.isConnected()).thenReturn(true); + when(device2.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE); + when(device2.getId()).thenReturn(TEST_DEVICE_2_ID); + mSelectedDevices.add(device); + mSelectableDevices.add(device2); + when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(device); + mDevices.add(device); + mDevices.add(device2); + when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mSelectedDevices); + when(mLocalMediaManager.getSelectableMediaDevice()).thenReturn(mSelectableDevices); + mMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices); + + final Slice mediaSlice = mMediaOutputSlice.getSlice(); + String sliceInfo = SliceQuery.findAll(mediaSlice, FORMAT_SLICE, HINT_LIST_ITEM, + null).toString(); + + assertThat(TextUtils.indexOf(sliceInfo, mContext.getText( + R.string.bluetooth_pairing_pref_title))).isEqualTo(-1); + } + @Test public void onNotifyChange_foundMediaDevice_connect() { mDevices.clear(); diff --git a/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java index 1a088c4a083..f78d42f558d 100644 --- a/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/sound/MediaOutputPreferenceControllerTest.java @@ -166,47 +166,6 @@ public class MediaOutputPreferenceControllerTest { ShadowBluetoothUtils.reset(); } - - /** - * A2DP Bluetooth device(s) are not connected nor previously connected - * Preference should be invisible - */ - @Test - public void updateState_withoutConnectedBtDevice_preferenceInvisible() { - mShadowAudioManager.setOutputDevice(DEVICE_OUT_EARPIECE); - mAudioManager.setMode(AudioManager.MODE_NORMAL); - mProfileConnectedDevices.clear(); - when(mA2dpProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); - mPreference.setVisible(true); - - assertThat(mPreference.isVisible()).isTrue(); - mController.updateState(mPreference); - assertThat(mPreference.isVisible()).isFalse(); - } - - /** - * A2DP Bluetooth device(s) are connected, no matter active or inactive - * Preference should be visible - */ - @Test - public void updateState_withConnectedBtDevice_preferenceVisible() { - mShadowAudioManager.setOutputDevice(DEVICE_OUT_BLUETOOTH_A2DP); - mAudioManager.setMode(AudioManager.MODE_NORMAL); - mProfileConnectedDevices.clear(); - mProfileConnectedDevices.add(mBluetoothDevice); - when(mA2dpProfile.getConnectedDevices()).thenReturn(mProfileConnectedDevices); - assertThat(mPreference.isVisible()).isFalse(); - - // Without Active Bluetooth Device - mController.updateState(mPreference); - assertThat(mPreference.isVisible()).isTrue(); - - // With Active Bluetooth Device - when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice); - mController.updateState(mPreference); - assertThat(mPreference.isVisible()).isTrue(); - } - /** * A2DP Bluetooth device(s) are connected, but no device is set as activated * Preference summary should be "This device" @@ -247,30 +206,6 @@ public class MediaOutputPreferenceControllerTest { assertThat(mPreference.getSummary()).isEqualTo(TEST_DEVICE_NAME_1); } - - /** - * Hearing Aid device(s) are connected, no matter active or inactive - * Preference should be visible - */ - @Test - public void updateState_withConnectedHADevice_preferenceVisible() { - mShadowAudioManager.setOutputDevice(DEVICE_OUT_HEARING_AID); - mAudioManager.setMode(AudioManager.MODE_NORMAL); - mHearingAidActiveDevices.clear(); - mHearingAidActiveDevices.add(mLeftBluetoothHapDevice); - when(mHearingAidProfile.getConnectedDevices()).thenReturn(mHearingAidActiveDevices); - assertThat(mPreference.isVisible()).isFalse(); - - // Without Active Hearing Aid Device - mController.updateState(mPreference); - assertThat(mPreference.isVisible()).isTrue(); - - // With Active Hearing Aid Device - when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices); - mController.updateState(mPreference); - assertThat(mPreference.isVisible()).isTrue(); - } - /** * Hearing Aid device(s) are connected and active * Preference summary should be device's name @@ -310,12 +245,12 @@ public class MediaOutputPreferenceControllerTest { * Summary should be default summary */ @Test - public void updateState_shouldSetSummary() { + public void updateState_notInCall_preferenceVisible() { + mAudioManager.setMode(AudioManager.MODE_NORMAL); + mController.updateState(mPreference); - assertThat(mPreference.isVisible()).isFalse(); - assertThat(mPreference.getSummary()).isEqualTo( - mContext.getText(R.string.media_output_default_summary)); + assertThat(mPreference.isVisible()).isTrue(); } /** @@ -324,14 +259,12 @@ public class MediaOutputPreferenceControllerTest { * Default string should be "Unavailable during calls" */ @Test - public void updateState_duringACall_shouldSetDefaultSummary() { + public void updateState_inCall_preferenceInvisible() { mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); mController.updateState(mPreference); assertThat(mPreference.isVisible()).isFalse(); - assertThat(mPreference.getSummary()).isEqualTo( - mContext.getText(R.string.media_out_summary_ongoing_call_state)); } @Test