Fix issues in BT detail header
1. Update isAvailable() in controller 2. Update method to get fast pair icon Bug: 124455912 Test: RunSettingsRoboTests Change-Id: I24a04c8c91d74e9b8b7e8746ad6279fafa37f0a9
This commit is contained in:
@@ -18,16 +18,19 @@ package com.android.settings.bluetooth;
|
|||||||
|
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.PorterDuffColorFilter;
|
import android.graphics.PorterDuffColorFilter;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.core.graphics.drawable.IconCompat;
|
|
||||||
import androidx.preference.PreferenceScreen;
|
import androidx.preference.PreferenceScreen;
|
||||||
|
|
||||||
import com.android.settings.R;
|
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.LifecycleObserver;
|
||||||
import com.android.settingslib.core.lifecycle.events.OnStart;
|
import com.android.settingslib.core.lifecycle.events.OnStart;
|
||||||
import com.android.settingslib.core.lifecycle.events.OnStop;
|
import com.android.settingslib.core.lifecycle.events.OnStop;
|
||||||
|
import com.android.settingslib.utils.ThreadUtils;
|
||||||
import com.android.settingslib.widget.LayoutPreference;
|
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.).
|
* This class adds a header with device name and status (connected/disconnected, etc.).
|
||||||
*/
|
*/
|
||||||
public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceController implements
|
public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceController implements
|
||||||
LifecycleObserver, OnStart, OnStop, CachedBluetoothDevice.Callback {
|
LifecycleObserver, OnStart, OnStop, CachedBluetoothDevice.Callback {
|
||||||
|
private static final String TAG = "AdvancedBtHeaderCtrl";
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
LayoutPreference mLayoutPreference;
|
LayoutPreference mLayoutPreference;
|
||||||
|
@VisibleForTesting
|
||||||
|
final Map<String, Bitmap> mIconCache;
|
||||||
private CachedBluetoothDevice mCachedDevice;
|
private CachedBluetoothDevice mCachedDevice;
|
||||||
|
|
||||||
public AdvancedBluetoothDetailsHeaderController(Context context, String prefKey) {
|
public AdvancedBluetoothDetailsHeaderController(Context context, String prefKey) {
|
||||||
super(context, prefKey);
|
super(context, prefKey);
|
||||||
|
mIconCache = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -65,6 +77,7 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
|||||||
super.displayPreference(screen);
|
super.displayPreference(screen);
|
||||||
mLayoutPreference = screen.findPreference(getPreferenceKey());
|
mLayoutPreference = screen.findPreference(getPreferenceKey());
|
||||||
mLayoutPreference.setVisible(isAvailable());
|
mLayoutPreference.setVisible(isAvailable());
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +89,14 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
|||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
mCachedDevice.unregisterCallback(this::onDeviceAttributesChanged);
|
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) {
|
public void init(CachedBluetoothDevice cachedBluetoothDevice) {
|
||||||
@@ -140,8 +161,7 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
|||||||
final String iconUri = Utils.getStringMetaData(bluetoothDevice, iconMetaKey);
|
final String iconUri = Utils.getStringMetaData(bluetoothDevice, iconMetaKey);
|
||||||
if (iconUri != null) {
|
if (iconUri != null) {
|
||||||
final ImageView imageView = linearLayout.findViewById(R.id.header_icon);
|
final ImageView imageView = linearLayout.findViewById(R.id.header_icon);
|
||||||
final IconCompat iconCompat = IconCompat.createWithContentUri(iconUri);
|
updateIcon(imageView, iconUri);
|
||||||
imageView.setImageBitmap(iconCompat.getBitmap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final int batteryLevel = Utils.getIntMetaData(bluetoothDevice, batteryMetaKey);
|
final int batteryLevel = Utils.getIntMetaData(bluetoothDevice, batteryMetaKey);
|
||||||
@@ -181,11 +201,35 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont
|
|||||||
BluetoothDevice.METADATA_MAIN_ICON);
|
BluetoothDevice.METADATA_MAIN_ICON);
|
||||||
if (iconUri != null) {
|
if (iconUri != null) {
|
||||||
final ImageView imageView = linearLayout.findViewById(R.id.header_icon);
|
final ImageView imageView = linearLayout.findViewById(R.id.header_icon);
|
||||||
final IconCompat iconCompat = IconCompat.createWithContentUri(iconUri);
|
updateIcon(imageView, iconUri);
|
||||||
imageView.setImageBitmap(iconCompat.getBitmap());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
@Override
|
||||||
public void onDeviceAttributesChanged() {
|
public void onDeviceAttributesChanged() {
|
||||||
if (mCachedDevice != null) {
|
if (mCachedDevice != null) {
|
||||||
|
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package com.android.settings.bluetooth;
|
package com.android.settings.bluetooth;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothDevice;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.preference.PreferenceFragmentCompat;
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
@@ -44,13 +43,6 @@ public class BluetoothDetailsButtonsController extends BluetoothDetailsControlle
|
|||||||
mIsConnected = device.isConnected();
|
mIsConnected = device.isConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable() {
|
|
||||||
final boolean unthetheredHeadset = Utils.getBooleanMetaData(mCachedDevice.getDevice(),
|
|
||||||
BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET);
|
|
||||||
return !unthetheredHeadset;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onForgetButtonPressed() {
|
private void onForgetButtonPressed() {
|
||||||
ForgetDeviceDialogFragment fragment =
|
ForgetDeviceDialogFragment fragment =
|
||||||
ForgetDeviceDialogFragment.newInstance(mCachedDevice.getAddress());
|
ForgetDeviceDialogFragment.newInstance(mCachedDevice.getAddress());
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.settings.bluetooth;
|
package com.android.settings.bluetooth;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
@@ -50,6 +51,12 @@ public class BluetoothDetailsHeaderController extends BluetoothDetailsController
|
|||||||
mDeviceManager = mLocalManager.getCachedDeviceManager();
|
mDeviceManager = mLocalManager.getCachedDeviceManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return !Utils.getBooleanMetaData(mCachedDevice.getDevice(),
|
||||||
|
BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void init(PreferenceScreen screen) {
|
protected void init(PreferenceScreen screen) {
|
||||||
final LayoutPreference headerPreference = screen.findPreference(KEY_DEVICE_HEADER);
|
final LayoutPreference headerPreference = screen.findPreference(KEY_DEVICE_HEADER);
|
||||||
|
@@ -18,13 +18,16 @@ package com.android.settings.bluetooth;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
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_MAIN = 30;
|
||||||
private static final int BATTERY_LEVEL_LEFT = 25;
|
private static final int BATTERY_LEVEL_LEFT = 25;
|
||||||
private static final int BATTERY_LEVEL_RIGHT = 45;
|
private static final int BATTERY_LEVEL_RIGHT = 45;
|
||||||
|
private static final String ICON_URI = "content://test.provider/icon.png";
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private BluetoothDevice mBluetoothDevice;
|
private BluetoothDevice mBluetoothDevice;
|
||||||
@Mock
|
@Mock
|
||||||
|
private Bitmap mBitmap;
|
||||||
|
@Mock
|
||||||
|
private ImageView mImageView;
|
||||||
|
@Mock
|
||||||
private CachedBluetoothDevice mCachedDevice;
|
private CachedBluetoothDevice mCachedDevice;
|
||||||
private AdvancedBluetoothDetailsHeaderController mController;
|
private AdvancedBluetoothDetailsHeaderController mController;
|
||||||
private LayoutPreference mLayoutPreference;
|
private LayoutPreference mLayoutPreference;
|
||||||
@@ -142,6 +150,15 @@ public class AdvancedBluetoothDetailsHeaderControllerTest{
|
|||||||
BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
|
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) {
|
private void assertBatteryLevel(LinearLayout linearLayout, int batteryLevel) {
|
||||||
final TextView textView = linearLayout.findViewById(R.id.bt_battery_summary);
|
final TextView textView = linearLayout.findViewById(R.id.bt_battery_summary);
|
||||||
assertThat(textView.getText().toString()).isEqualTo(
|
assertThat(textView.getText().toString()).isEqualTo(
|
||||||
|
@@ -19,18 +19,12 @@ package com.android.settings.bluetooth;
|
|||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
|
||||||
import static org.mockito.Mockito.inOrder;
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
import android.graphics.drawable.Drawable;
|
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.R;
|
||||||
import com.android.settings.testutils.FakeFeatureFactory;
|
import com.android.settings.testutils.FakeFeatureFactory;
|
||||||
@@ -62,6 +56,8 @@ public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsContro
|
|||||||
private LocalBluetoothManager mBluetoothManager;
|
private LocalBluetoothManager mBluetoothManager;
|
||||||
@Mock
|
@Mock
|
||||||
private CachedBluetoothDeviceManager mCachedDeviceManager;
|
private CachedBluetoothDeviceManager mCachedDeviceManager;
|
||||||
|
@Mock
|
||||||
|
private BluetoothDevice mBluetoothDevice;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
@@ -77,6 +73,7 @@ public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsContro
|
|||||||
mPreference.setKey(mController.getPreferenceKey());
|
mPreference.setKey(mController.getPreferenceKey());
|
||||||
mScreen.addPreference(mPreference);
|
mScreen.addPreference(mPreference);
|
||||||
setupDevice(mDeviceConfig);
|
setupDevice(mDeviceConfig);
|
||||||
|
when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -124,4 +121,12 @@ public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsContro
|
|||||||
inOrder.verify(mHeaderController)
|
inOrder.verify(mHeaderController)
|
||||||
.setSummary(mContext.getString(R.string.bluetooth_connecting));
|
.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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user