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

Commit d5e653e4 authored by Lei Yu's avatar Lei Yu Committed by Android (Google) Code Review
Browse files

Merge "Fix issues in BT detail header"

parents 85ead953 5e5dd25b
Loading
Loading
Loading
Loading
+49 −5
Original line number Diff line number Diff line
@@ -18,16 +18,19 @@ package com.android.settings.bluetooth;

import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
@@ -37,20 +40,29 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.LayoutPreference;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * This class adds a header with device name and status (connected/disconnected, etc.).
 */
public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceController implements
        LifecycleObserver, OnStart, OnStop, CachedBluetoothDevice.Callback {
    private static final String TAG = "AdvancedBtHeaderCtrl";

    @VisibleForTesting
    LayoutPreference mLayoutPreference;
    @VisibleForTesting
    final Map<String, Bitmap> mIconCache;
    private CachedBluetoothDevice mCachedDevice;

    public AdvancedBluetoothDetailsHeaderController(Context context, String prefKey) {
        super(context, prefKey);
        mIconCache = new HashMap<>();
    }

    @Override
@@ -65,6 +77,7 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
        super.displayPreference(screen);
        mLayoutPreference = screen.findPreference(getPreferenceKey());
        mLayoutPreference.setVisible(isAvailable());

        refresh();
    }

@@ -76,6 +89,14 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
    @Override
    public void onStop() {
        mCachedDevice.unregisterCallback(this::onDeviceAttributesChanged);

        // Destroy icon bitmap associated with this header
        for (Bitmap bitmap : mIconCache.values()) {
            if (bitmap != null) {
                bitmap.recycle();
            }
        }
        mIconCache.clear();
    }

    public void init(CachedBluetoothDevice cachedBluetoothDevice) {
@@ -140,8 +161,7 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
        final String iconUri = Utils.getStringMetaData(bluetoothDevice, iconMetaKey);
        if (iconUri != null) {
            final ImageView imageView = linearLayout.findViewById(R.id.header_icon);
            final IconCompat iconCompat = IconCompat.createWithContentUri(iconUri);
            imageView.setImageBitmap(iconCompat.getBitmap());
            updateIcon(imageView, iconUri);
        }

        final int batteryLevel = Utils.getIntMetaData(bluetoothDevice, batteryMetaKey);
@@ -181,9 +201,33 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
                BluetoothDevice.METADATA_MAIN_ICON);
        if (iconUri != null) {
            final ImageView imageView = linearLayout.findViewById(R.id.header_icon);
            final IconCompat iconCompat = IconCompat.createWithContentUri(iconUri);
            imageView.setImageBitmap(iconCompat.getBitmap());
            updateIcon(imageView, iconUri);
        }
    }

    /**
     * Update icon by {@code iconUri}. If icon exists in cache, use it; otherwise extract it
     * from uri in background thread and update it in main thread.
     */
    @VisibleForTesting
    void updateIcon(ImageView imageView, String iconUri) {
        if (mIconCache.containsKey(iconUri)) {
            imageView.setImageBitmap(mIconCache.get(iconUri));
            return;
        }

        ThreadUtils.postOnBackgroundThread(() -> {
            try {
                final Bitmap bitmap = MediaStore.Images.Media.getBitmap(
                        mContext.getContentResolver(), Uri.parse(iconUri));
                ThreadUtils.postOnMainThread(() -> {
                    mIconCache.put(iconUri, bitmap);
                    imageView.setImageBitmap(bitmap);
                });
            } catch (IOException e) {
                Log.e(TAG, "Failed to get bitmap for: " + iconUri);
            }
        });
    }

    @Override
+0 −8
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.settings.bluetooth;

import android.bluetooth.BluetoothDevice;
import android.content.Context;

import androidx.preference.PreferenceFragmentCompat;
@@ -44,13 +43,6 @@ public class BluetoothDetailsButtonsController extends BluetoothDetailsControlle
        mIsConnected = device.isConnected();
    }

    @Override
    public boolean isAvailable() {
        final boolean unthetheredHeadset = Utils.getBooleanMetaData(mCachedDevice.getDevice(),
                BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET);
        return !unthetheredHeadset;
    }

    private void onForgetButtonPressed() {
        ForgetDeviceDialogFragment fragment =
                ForgetDeviceDialogFragment.newInstance(mCachedDevice.getAddress());
+7 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.settings.bluetooth;

import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Pair;
@@ -50,6 +51,12 @@ public class BluetoothDetailsHeaderController extends BluetoothDetailsController
        mDeviceManager = mLocalManager.getCachedDeviceManager();
    }

    @Override
    public boolean isAvailable() {
        return !Utils.getBooleanMetaData(mCachedDevice.getDevice(),
                BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET);
    }

    @Override
    protected void init(PreferenceScreen screen) {
        final LayoutPreference headerPreference = screen.findPreference(KEY_DEVICE_HEADER);
+17 −0
Original line number Diff line number Diff line
@@ -18,13 +18,16 @@ package com.android.settings.bluetooth;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

@@ -50,12 +53,17 @@ public class AdvancedBluetoothDetailsHeaderControllerTest{
    private static final int BATTERY_LEVEL_MAIN = 30;
    private static final int BATTERY_LEVEL_LEFT = 25;
    private static final int BATTERY_LEVEL_RIGHT = 45;
    private static final String ICON_URI = "content://test.provider/icon.png";

    private Context mContext;

    @Mock
    private BluetoothDevice mBluetoothDevice;
    @Mock
    private Bitmap mBitmap;
    @Mock
    private ImageView mImageView;
    @Mock
    private CachedBluetoothDevice mCachedDevice;
    private AdvancedBluetoothDetailsHeaderController mController;
    private LayoutPreference mLayoutPreference;
@@ -142,6 +150,15 @@ public class AdvancedBluetoothDetailsHeaderControllerTest{
                BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
    }

    @Test
    public void updateIcon_existInCache_setImageBitmap() {
        mController.mIconCache.put(ICON_URI, mBitmap);

        mController.updateIcon(mImageView, ICON_URI);

        verify(mImageView).setImageBitmap(mBitmap);
    }

    private void assertBatteryLevel(LinearLayout linearLayout, int batteryLevel) {
        final TextView textView = linearLayout.findViewById(R.id.bt_battery_summary);
        assertThat(textView.getText().toString()).isEqualTo(
+12 −7
Original line number Diff line number Diff line
@@ -19,18 +19,12 @@ package com.android.settings.bluetooth;
import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.bluetooth.BluetoothDevice;
import android.graphics.drawable.Drawable;
import android.view.View;

import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

import com.android.settings.R;
import com.android.settings.testutils.FakeFeatureFactory;
@@ -62,6 +56,8 @@ public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsContro
    private LocalBluetoothManager mBluetoothManager;
    @Mock
    private CachedBluetoothDeviceManager mCachedDeviceManager;
    @Mock
    private BluetoothDevice mBluetoothDevice;

    @Override
    public void setUp() {
@@ -77,6 +73,7 @@ public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsContro
        mPreference.setKey(mController.getPreferenceKey());
        mScreen.addPreference(mPreference);
        setupDevice(mDeviceConfig);
        when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
    }

    @After
@@ -124,4 +121,12 @@ public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsContro
        inOrder.verify(mHeaderController)
            .setSummary(mContext.getString(R.string.bluetooth_connecting));
    }

    @Test
    public void isAvailable_unthetheredHeadset_returnFalse() {
        when(mBluetoothDevice.getMetadata(
                BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)).thenReturn("true");

        assertThat(mController.isAvailable()).isFalse();
    }
}