blob: 5c1fbdd3ac6373c59347a574829fd4e8fa9fc286 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.homepage.contextualcards.slices;
import android.annotation.ColorInt;
import android.app.PendingIntent;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothAdapter;
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.os.Bundle;
import android.util.Log;
import android.util.Pair;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice;
import androidx.slice.builders.ListBuilder;
import androidx.slice.builders.SliceAction;
import com.android.internal.annotations.VisibleForTesting;
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.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.slices.CustomSliceRegistry;
import com.android.settings.slices.CustomSliceable;
import com.android.settings.slices.SliceBroadcastReceiver;
import com.android.settings.slices.SliceBuilderUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class BluetoothDevicesSlice implements CustomSliceable {
@VisibleForTesting
static final String BLUETOOTH_DEVICE_HASH_CODE = "bluetooth_device_hash_code";
/**
* Add the "Pair new device" in the end of slice, when the number of Bluetooth devices is less
* than {@link #DEFAULT_EXPANDED_ROW_COUNT}.
*/
@VisibleForTesting
static final int DEFAULT_EXPANDED_ROW_COUNT = 3;
/**
* 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 String TAG = "BluetoothDevicesSlice";
private final Context mContext;
public BluetoothDevicesSlice(Context context) {
mContext = context;
}
@Override
public Uri getUri() {
return CustomSliceRegistry.BLUETOOTH_DEVICES_SLICE_URI;
}
@Override
public Slice getSlice() {
// 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 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);
// 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)
.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));
}
return listBuilder.build();
}
@Override
public Intent getIntent() {
final String screenTitle = mContext.getText(R.string.connected_devices_dashboard_title)
.toString();
return SliceBuilderUtils.buildSearchResultPageIntent(mContext,
ConnectedDeviceDashboardFragment.class.getName(), "" /* key */,
screenTitle,
SettingsEnums.SLICE)
.setClassName(mContext.getPackageName(), SubSettings.class.getName())
.setData(getUri());
}
@Override
public void onNotifyChange(Intent intent) {
// Activate available media device.
final int bluetoothDeviceHashCode = intent.getIntExtra(BLUETOOTH_DEVICE_HASH_CODE, -1);
for (CachedBluetoothDevice cachedBluetoothDevice : getConnectedBluetoothDevices()) {
if (cachedBluetoothDevice.hashCode() == bluetoothDeviceHashCode) {
cachedBluetoothDevice.setActive();
return;
}
}
}
@Override
public Class getBackgroundWorkerClass() {
return BluetoothUpdateWorker.class;
}
@VisibleForTesting
List<CachedBluetoothDevice> getConnectedBluetoothDevices() {
final List<CachedBluetoothDevice> bluetoothDeviceList = new ArrayList<>();
// If Bluetooth is disable, skip to get the Bluetooth devices.
if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) {
Log.i(TAG, "Cannot get Bluetooth devices, Bluetooth is disabled.");
return bluetoothDeviceList;
}
// Get the Bluetooth devices from LocalBluetoothManager.
final LocalBluetoothManager bluetoothManager =
com.android.settings.bluetooth.Utils.getLocalBtManager(mContext);
if (bluetoothManager == null) {
Log.i(TAG, "Cannot get Bluetooth devices, Bluetooth is unsupported.");
return bluetoothDeviceList;
}
final Collection<CachedBluetoothDevice> cachedDevices =
bluetoothManager.getCachedDeviceManager().getCachedDevicesCopy();
// Get all connected devices and sort them.
return cachedDevices.stream()
.filter(device -> device.getDevice().isConnected())
.sorted(COMPARATOR).collect(Collectors.toList());
}
@VisibleForTesting
PendingIntent getBluetoothDetailIntent(CachedBluetoothDevice device) {
final Bundle args = new Bundle();
args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS,
device.getDevice().getAddress());
final SubSettingLauncher subSettingLauncher = new SubSettingLauncher(mContext);
subSettingLauncher.setDestination(BluetoothDeviceDetailsFragment.class.getName())
.setArguments(args)
.setTitleRes(R.string.device_details_title)
.setSourceMetricsCategory(SettingsEnums.BLUETOOTH_DEVICE_DETAILS);
// The requestCode should be unique, use the hashcode of device as request code.
return PendingIntent
.getActivity(mContext, device.hashCode() /* requestCode */,
subSettingLauncher.toIntent(),
0 /* flags */);
}
@VisibleForTesting
IconCompat getBluetoothDeviceIcon(CachedBluetoothDevice device) {
final Pair<Drawable, String> pair =
com.android.settings.bluetooth.Utils.getBtRainbowDrawableWithDescription(mContext,
device);
final Drawable drawable = pair.first;
// Use default bluetooth icon if can't get icon.
if (drawable == null) {
return IconCompat.createWithResource(mContext,
com.android.internal.R.drawable.ic_settings_bluetooth);
}
// Tint icon: Accent color for connected state; Disable color for busy state.
@ColorInt int color = Utils.getColorAccentDefaultColor(mContext);
if (device.isBusy()) {
color = Utils.getDisabled(mContext,
Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal));
}
drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
return Utils.createIconWithDrawable(drawable);
}
private List<ListBuilder.RowBuilder> getBluetoothRowBuilder() {
// According to Bluetooth devices to create row builders.
final List<ListBuilder.RowBuilder> bluetoothRows = new ArrayList<>();
final List<CachedBluetoothDevice> bluetoothDevices = getConnectedBluetoothDevices();
for (CachedBluetoothDevice bluetoothDevice : bluetoothDevices) {
final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder()
.setTitleItem(getBluetoothDeviceIcon(bluetoothDevice), ListBuilder.ICON_IMAGE)
.setTitle(bluetoothDevice.getName())
.setSubtitle(bluetoothDevice.getConnectionSummary());
if (bluetoothDevice.isConnectedA2dpDevice()) {
// For available media devices, the primary action is to activate audio stream and
// add setting icon to the end to link detail page.
rowBuilder.setPrimaryAction(buildMediaBluetoothAction(bluetoothDevice));
rowBuilder.addEndItem(buildBluetoothDetailDeepLinkAction(bluetoothDevice));
} else {
// For other devices, the primary action is to link detail page.
rowBuilder.setPrimaryAction(buildBluetoothDetailDeepLinkAction(bluetoothDevice));
}
bluetoothRows.add(rowBuilder);
}
return bluetoothRows;
}
@VisibleForTesting
SliceAction buildMediaBluetoothAction(CachedBluetoothDevice bluetoothDevice) {
// Send broadcast to activate available media device.
final Intent intent = new Intent(getUri().toString())
.setClass(mContext, SliceBroadcastReceiver.class)
.putExtra(BLUETOOTH_DEVICE_HASH_CODE, bluetoothDevice.hashCode());
return SliceAction.create(
PendingIntent.getBroadcast(mContext, bluetoothDevice.hashCode(), intent, 0),
getBluetoothDeviceIcon(bluetoothDevice),
ListBuilder.ICON_IMAGE,
bluetoothDevice.getName());
}
@VisibleForTesting
SliceAction buildBluetoothDetailDeepLinkAction(CachedBluetoothDevice bluetoothDevice) {
return SliceAction.createDeeplink(
getBluetoothDetailIntent(bluetoothDevice),
IconCompat.createWithResource(mContext, R.drawable.ic_settings_accent),
ListBuilder.ICON_IMAGE,
bluetoothDevice.getName());
}
private CharSequence getSubTitle(int deviceCount) {
return mContext.getResources().getQuantityString(R.plurals.show_bluetooth_devices,
deviceCount, deviceCount);
}
}