Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 42239028 authored by Jason Chiu's avatar Jason Chiu Committed by Android (Google) Code Review
Browse files

Merge "Implement new design of Bluetooth card"

parents fd09f64d 75e8f5ea
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -391,6 +391,8 @@
    <!-- Maximum height for SliceView, override on slices/view/src/main/res/values/dimens.xml -->
    <!-- A single Row Slice height is 60dp -->
    <dimen name="abc_slice_large_height">1200dp</dimen>
    <!-- Min height of slice row view, override on slices/view/src/main/res/values/dimens.xml -->
    <dimen name="abc_slice_row_min_height">@dimen/abc_slice_row_max_height</dimen>

    <!-- System navigation settings illustration height -->
    <dimen name="system_navigation_illustration_height">320dp</dimen>
+0 −10
Original line number Diff line number Diff line
@@ -144,8 +144,6 @@
    <string name="bluetooth_lock_voice_dialing_summary">
      Prevent use of the bluetooth dialer when the screen is locked
    </string>
    <!-- Bluetooth settings screen, heading above the list of nearby bluetooth devices -->
    <string name="bluetooth_devices">Bluetooth devices</string>
    <!-- Bluetooth settings screen, title for the current bluetooth name setting -->
    <string name="bluetooth_device_name">Device name</string>
    <!-- Bluetooth settings screen, image description for device details button. This opens the screen to rename, unpair, etc. a single device. -->
@@ -11311,14 +11309,6 @@
    <!-- Message for Network connection connecting progress Dialog. Try to connect to wifi ap.[CHAR LIMIT=40] -->
    <string name="network_connection_connecting_message">Connecting to device\u2026</string>
    <!-- Summary for bluetooth devices count in Bluetooth devices slice. [CHAR LIMIT=NONE] -->
    <plurals name="show_bluetooth_devices">
        <item quantity="one"><xliff:g id="number_device_count">%1$d</xliff:g> device connected</item>
        <item quantity="other"><xliff:g id="number_device_count">%1$d</xliff:g> devices connected</item>
    </plurals>
    <!-- Title for no bluetooth devices in Bluetooth devices slice. [CHAR LIMIT=NONE] -->
    <string name="no_bluetooth_devices">No Bluetooth devices</string>
    <!-- Title for left bluetooth device. [CHAR LIMIT=NONE] -->
    <string name="bluetooth_left_name">Left</string>
    <!-- Title for right bluetooth device. [CHAR LIMIT=NONE] -->
+74 −25
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.settings.homepage.contextualcards.slices;

import static android.app.slice.Slice.EXTRA_TOGGLE_STATE;

import android.app.PendingIntent;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
@@ -38,6 +40,7 @@ import com.android.settings.R;
import com.android.settings.SubSettings;
import com.android.settings.Utils;
import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment;
import com.android.settings.bluetooth.BluetoothPairingDetail;
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.slices.CustomSliceRegistry;
@@ -64,17 +67,18 @@ public class BluetoothDevicesSlice implements CustomSliceable {
     * than {@link #DEFAULT_EXPANDED_ROW_COUNT}.
     */
    @VisibleForTesting
    static final int DEFAULT_EXPANDED_ROW_COUNT = 3;
    static final int DEFAULT_EXPANDED_ROW_COUNT = 2;

    /**
     * Refer {@link com.android.settings.bluetooth.BluetoothDevicePreference#compareTo} to sort the
     * Bluetooth devices by {@link CachedBluetoothDevice}.
     */
    private static final Comparator<CachedBluetoothDevice> COMPARATOR
            = Comparator.naturalOrder();
    private static final Comparator<CachedBluetoothDevice> COMPARATOR = Comparator.naturalOrder();

    private static final String TAG = "BluetoothDevicesSlice";

    private static int sToggledState;

    private final Context mContext;

    public BluetoothDevicesSlice(Context context) {
@@ -88,43 +92,52 @@ public class BluetoothDevicesSlice implements CustomSliceable {

    @Override
    public Slice getSlice() {
        final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
        if (btAdapter == null) {
            Log.i(TAG, "Bluetooth is not supported on this hardware platform");
            return null;
        }

        // Reload theme for switching dark mode on/off
        mContext.getTheme().applyStyle(R.style.Theme_Settings_Home, true /* force */);

        final IconCompat icon = IconCompat.createWithResource(mContext,
                com.android.internal.R.drawable.ic_settings_bluetooth);
        final CharSequence title = mContext.getText(R.string.bluetooth_devices);
        final CharSequence titleNoBluetoothDevices = mContext.getText(
                R.string.no_bluetooth_devices);
        final CharSequence title = mContext.getText(R.string.bluetooth_settings_title);
        final PendingIntent primaryActionIntent = PendingIntent.getActivity(mContext, 0,
                getIntent(), 0);
        final SliceAction primarySliceAction = SliceAction.createDeeplink(primaryActionIntent, icon,
                ListBuilder.ICON_IMAGE, title);
        final ListBuilder listBuilder =
                new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
                        .setAccentColor(COLOR_NOT_TINTED);
        final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
                .setAccentColor(COLOR_NOT_TINTED)
                .setHeader(new ListBuilder.HeaderBuilder()
                        .setTitle(title)
                        .setPrimaryAction(primarySliceAction));

        // Only show a toggle when Bluetooth is off and not turning on.
        if ((!isBluetoothEnabled(btAdapter) && sToggledState != BluetoothAdapter.STATE_TURNING_ON)
                || sToggledState == BluetoothAdapter.STATE_TURNING_OFF) {
            sToggledState = 0;
            final PendingIntent toggleAction = getBroadcastIntent(mContext);
            final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction,
                    null /* actionTitle */, false /* isChecked */);
            return listBuilder
                    .addAction(toggleSliceAction)
                    .build();
        }
        sToggledState = 0;

        // Get row builders by Bluetooth devices.
        final List<ListBuilder.RowBuilder> rows = getBluetoothRowBuilder();

        // Return a header with IsError flag, if no Bluetooth devices.
        if (rows.isEmpty()) {
            return listBuilder.setHeader(new ListBuilder.HeaderBuilder()
                    .setTitle(titleNoBluetoothDevices)
                    .setPrimaryAction(primarySliceAction))
                    .setIsError(true)
            return listBuilder
                    .addRow(getPairNewDeviceRow())
                    .build();
        }

        // Get displayable device count.
        final int deviceCount = Math.min(rows.size(), DEFAULT_EXPANDED_ROW_COUNT);

        // According to the displayable device count to set sub title of header.
        listBuilder.setHeader(new ListBuilder.HeaderBuilder()
                .setTitle(title)
                .setSubtitle(getSubTitle(deviceCount))
                .setPrimaryAction(primarySliceAction));

        // According to the displayable device count to add bluetooth device rows.
        for (int i = 0; i < deviceCount; i++) {
            listBuilder.addRow(rows.get(i));
@@ -148,6 +161,20 @@ public class BluetoothDevicesSlice implements CustomSliceable {

    @Override
    public void onNotifyChange(Intent intent) {
        final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
        final boolean currentState = isBluetoothEnabled(btAdapter);
        final boolean newState = intent.getBooleanExtra(EXTRA_TOGGLE_STATE, currentState);
        if (newState != currentState) {
            if (newState) {
                sToggledState = BluetoothAdapter.STATE_TURNING_ON;
                btAdapter.enable();
            } else {
                sToggledState = BluetoothAdapter.STATE_TURNING_OFF;
                btAdapter.disable();
            }
            mContext.getContentResolver().notifyChange(getUri(), null);
        }

        // Activate available media device.
        final int bluetoothDeviceHashCode = intent.getIntExtra(BLUETOOTH_DEVICE_HASH_CODE, -1);
        for (CachedBluetoothDevice cachedBluetoothDevice : getConnectedBluetoothDevices()) {
@@ -223,6 +250,23 @@ public class BluetoothDevicesSlice implements CustomSliceable {
        return Utils.createIconWithDrawable(drawable);
    }

    private ListBuilder.RowBuilder getPairNewDeviceRow() {
        final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_add_24dp);
        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.BLUETOOTH_PAIRING)
                .toIntent();
        final PendingIntent pi = PendingIntent.getActivity(mContext, intent.hashCode(), intent,
                0 /* flags */);
        final SliceAction action = SliceAction.createDeeplink(pi, icon, ListBuilder.ICON_IMAGE,
                title);
        return new ListBuilder.RowBuilder()
                .setTitleItem(action)
                .setTitle(title);
    }

    private List<ListBuilder.RowBuilder> getBluetoothRowBuilder() {
        // According to Bluetooth devices to create row builders.
        final List<ListBuilder.RowBuilder> bluetoothRows = new ArrayList<>();
@@ -272,8 +316,13 @@ public class BluetoothDevicesSlice implements CustomSliceable {
                bluetoothDevice.getName());
    }

    private CharSequence getSubTitle(int deviceCount) {
        return mContext.getResources().getQuantityString(R.plurals.show_bluetooth_devices,
                deviceCount, deviceCount);
    private boolean isBluetoothEnabled(BluetoothAdapter btAdapter) {
        switch (btAdapter.getState()) {
            case BluetoothAdapter.STATE_ON:
            case BluetoothAdapter.STATE_TURNING_ON:
                return true;
            default:
                return false;
        }
    }
}
+125 −29
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.settings.homepage.contextualcards.slices;

import static android.app.slice.Slice.EXTRA_TOGGLE_STATE;
import static android.app.slice.Slice.HINT_LIST_ITEM;
import static android.app.slice.SliceItem.FORMAT_SLICE;

@@ -26,9 +27,9 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;

@@ -37,11 +38,13 @@ import androidx.slice.Slice;
import androidx.slice.SliceItem;
import androidx.slice.SliceMetadata;
import androidx.slice.SliceProvider;
import androidx.slice.core.SliceAction;
import androidx.slice.core.SliceQuery;
import androidx.slice.widget.SliceLiveData;

import com.android.settings.R;
import com.android.settings.testutils.SliceTester;
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;

import org.junit.After;
@@ -52,6 +55,10 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;

import java.util.ArrayList;
import java.util.List;
@@ -101,18 +108,105 @@ public class BluetoothDevicesSliceTest {
    }

    @Test
    public void getSlice_hasBluetoothDevices_shouldHaveBluetoothDevicesTitle() {
    @Config(shadows = ShadowNoBluetoothAdapter.class)
    public void getSlice_noBluetoothHardware_shouldReturnNull() {
        final Slice slice = mBluetoothDevicesSlice.getSlice();

        assertThat(slice).isNull();
    }

    @Test
    @Config(shadows = ShadowBluetoothAdapter.class)
    public void getSlice_bluetoothOff_shouldHaveToggle() {
        final ShadowBluetoothAdapter adapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        adapter.setState(BluetoothAdapter.STATE_OFF);

        final Slice slice = mBluetoothDevicesSlice.getSlice();

        final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
        assertTitleAndIcon(metadata);
        final List<SliceAction> toggles = metadata.getToggles();
        assertThat(toggles).hasSize(1);
    }

    @Test
    @Config(shadows = ShadowBluetoothAdapter.class)
    public void getSlice_bluetoothOn_shouldNotHaveToggle() {
        final ShadowBluetoothAdapter adapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        adapter.setState(BluetoothAdapter.STATE_ON);

        final Slice slice = mBluetoothDevicesSlice.getSlice();

        final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
        assertTitleAndIcon(metadata);
        final List<SliceAction> toggles = metadata.getToggles();
        assertThat(toggles).isEmpty();
    }

    @Test
    @Config(shadows = ShadowBluetoothAdapter.class)
    public void getSlice_bluetoothTurningOff_shouldHaveToggle() {
        final ShadowBluetoothAdapter adapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        adapter.setState(BluetoothAdapter.STATE_ON);
        final Intent intent = new Intent().putExtra(EXTRA_TOGGLE_STATE, false);

        mBluetoothDevicesSlice.onNotifyChange(intent);
        final Slice slice = mBluetoothDevicesSlice.getSlice();

        final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
        final List<SliceAction> toggles = metadata.getToggles();
        assertThat(toggles).hasSize(1);
    }

    @Test
    @Config(shadows = ShadowBluetoothAdapter.class)
    public void getSlice_bluetoothTurningOn_shouldHaveToggle() {
        final ShadowBluetoothAdapter adapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        adapter.setState(BluetoothAdapter.STATE_OFF);
        final Intent intent = new Intent().putExtra(EXTRA_TOGGLE_STATE, true);

        mBluetoothDevicesSlice.onNotifyChange(intent);
        final Slice slice = mBluetoothDevicesSlice.getSlice();

        final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
        final List<SliceAction> toggles = metadata.getToggles();
        assertThat(toggles).isEmpty();
    }

    @Test
    @Config(shadows = ShadowBluetoothAdapter.class)
    public void getSlice_noBluetoothDevice_shouldHavePairNewDeviceRow() {
        final ShadowBluetoothAdapter adapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        adapter.setState(BluetoothAdapter.STATE_ON);
        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();

        final Slice slice = mBluetoothDevicesSlice.getSlice();

        final List<SliceItem> sliceItems = slice.getItems();
        SliceTester.assertAnySliceItemContainsTitle(sliceItems, mContext.getString(
                R.string.bluetooth_pairing_pref_title));
    }

    @Test
    @Config(shadows = ShadowBluetoothAdapter.class)
    public void getSlice_hasBluetoothDevices_shouldNotHavePairNewDeviceRow() {
        final ShadowBluetoothAdapter adapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        adapter.setState(BluetoothAdapter.STATE_ON);
        mockBluetoothDeviceList(1);
        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();

        final Slice slice = mBluetoothDevicesSlice.getSlice();

        final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
        assertThat(metadata.getTitle()).isEqualTo(mContext.getString(R.string.bluetooth_devices));
        final List<SliceItem> sliceItems = slice.getItems();
        SliceTester.assertNoSliceItemContainsTitle(sliceItems, mContext.getString(
                R.string.bluetooth_pairing_pref_title));
    }

    @Test
    @Config(shadows = ShadowBluetoothAdapter.class)
    public void getSlice_hasBluetoothDevices_shouldMatchBluetoothMockTitle() {
        final ShadowBluetoothAdapter adapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        adapter.setState(BluetoothAdapter.STATE_ON);
        mockBluetoothDeviceList(1);
        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();

@@ -123,7 +217,10 @@ public class BluetoothDevicesSliceTest {
    }

    @Test
    @Config(shadows = ShadowBluetoothAdapter.class)
    public void getSlice_hasMediaBluetoothDevice_shouldBuildMediaBluetoothAction() {
        final ShadowBluetoothAdapter adapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        adapter.setState(BluetoothAdapter.STATE_ON);
        mockBluetoothDeviceList(1 /* deviceCount */);
        doReturn(true).when(mBluetoothDeviceList.get(0)).isConnectedA2dpDevice();
        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();
@@ -134,7 +231,10 @@ public class BluetoothDevicesSliceTest {
    }

    @Test
    @Config(shadows = ShadowBluetoothAdapter.class)
    public void getSlice_noMediaBluetoothDevice_shouldNotBuildMediaBluetoothAction() {
        final ShadowBluetoothAdapter adapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        adapter.setState(BluetoothAdapter.STATE_ON);
        mockBluetoothDeviceList(1 /* deviceCount */);
        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();

@@ -144,18 +244,10 @@ public class BluetoothDevicesSliceTest {
    }

    @Test
    public void getSlice_noBluetoothDevices_shouldHaveNoBluetoothDevicesTitle() {
        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();

        final Slice slice = mBluetoothDevicesSlice.getSlice();

        final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
        assertThat(metadata.getTitle()).isEqualTo(
                mContext.getString(R.string.no_bluetooth_devices));
    }

    @Test
    @Config(shadows = ShadowBluetoothAdapter.class)
    public void getSlice_exceedDefaultRowCount_shouldOnlyShowDefaultRows() {
        final ShadowBluetoothAdapter adapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        adapter.setState(BluetoothAdapter.STATE_ON);
        mockBluetoothDeviceList(BluetoothDevicesSlice.DEFAULT_EXPANDED_ROW_COUNT + 1);
        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();

@@ -166,20 +258,6 @@ public class BluetoothDevicesSliceTest {
        assertThat(rows).isEqualTo(BluetoothDevicesSlice.DEFAULT_EXPANDED_ROW_COUNT);
    }

    @Test
    public void getSlice_exceedDefaultRowCount_shouldContainDefaultCountInSubTitle() {
        mockBluetoothDeviceList(BluetoothDevicesSlice.DEFAULT_EXPANDED_ROW_COUNT + 1);
        doReturn(mBluetoothDeviceList).when(mBluetoothDevicesSlice).getConnectedBluetoothDevices();

        final Slice slice = mBluetoothDevicesSlice.getSlice();

        final SliceMetadata metadata = SliceMetadata.from(mContext, slice);
        assertThat(metadata.getSubtitle()).isEqualTo(
                mContext.getResources().getQuantityString(R.plurals.show_bluetooth_devices,
                        BluetoothDevicesSlice.DEFAULT_EXPANDED_ROW_COUNT,
                        BluetoothDevicesSlice.DEFAULT_EXPANDED_ROW_COUNT));
    }

    @Test
    public void onNotifyChange_mediaDevice_shouldActivateDevice() {
        mockBluetoothDeviceList(1);
@@ -201,4 +279,22 @@ public class BluetoothDevicesSliceTest {
            mBluetoothDeviceList.add(mCachedBluetoothDevice);
        }
    }

    private void assertTitleAndIcon(SliceMetadata metadata) {
        assertThat(metadata.getTitle()).isEqualTo(mContext.getString(
                R.string.bluetooth_settings_title));

        final SliceAction primaryAction = metadata.getPrimaryAction();
        final IconCompat expectedToggleIcon = IconCompat.createWithResource(mContext,
                com.android.internal.R.drawable.ic_settings_bluetooth);
        assertThat(primaryAction.getIcon().toString()).isEqualTo(expectedToggleIcon.toString());
    }

    @Implements(BluetoothAdapter.class)
    public static class ShadowNoBluetoothAdapter extends ShadowBluetoothAdapter {
        @Implementation
        protected static BluetoothAdapter getDefaultAdapter() {
            return null;
        }
    }
}
+0 −1
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.ContentResolver;
import android.content.Context;
Loading