blob: 03a66f14307ef1e72453b1bc68d14294c57817f2 [file] [log] [blame]
/*
* Copyright (C) 2021 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.google.android.tv.btservices.settings;
import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_SLICE_FOLLOWUP;
import static com.google.android.tv.btservices.settings.BluetoothDevicePreferenceFragment.CONT_CANCEL_ARGS;
import static com.google.android.tv.btservices.settings.BluetoothDevicePreferenceFragment.KEY_CONNECT;
import static com.google.android.tv.btservices.settings.BluetoothDevicePreferenceFragment.KEY_DISCONNECT;
import static com.google.android.tv.btservices.settings.BluetoothDevicePreferenceFragment.KEY_FORGET;
import static com.google.android.tv.btservices.settings.BluetoothDevicePreferenceFragment.KEY_RENAME;
import static com.google.android.tv.btservices.settings.BluetoothDevicePreferenceFragment.KEY_UPDATE;
import static com.google.android.tv.btservices.settings.BluetoothDevicePreferenceFragment.YES_NO_ARGS;
import static com.google.android.tv.btservices.settings.ConnectedDevicesPreferenceFragment.KEY_ACCESSORIES;
import static com.google.android.tv.btservices.settings.ConnectedDevicesPreferenceFragment.KEY_AXEL_TOGGLE;
import static com.google.android.tv.btservices.settings.ConnectedDevicesPreferenceFragment.KEY_CEC_TOGGLE;
import static com.google.android.tv.btservices.settings.ConnectedDevicesPreferenceFragment.KEY_DEVICE_CONTROL;
import static com.google.android.tv.btservices.settings.ConnectedDevicesPreferenceFragment.KEY_OFFICIAL_REMOTES;
import static com.google.android.tv.btservices.settings.ConnectedDevicesPreferenceFragment.KEY_PAIR_REMOTE;
import static com.google.android.tv.btservices.settings.SliceBroadcastReceiver.CEC;
import static com.google.android.tv.btservices.settings.SliceBroadcastReceiver.TOGGLE_STATE;
import static com.google.android.tv.btservices.settings.SliceBroadcastReceiver.TOGGLE_TYPE;
import static com.google.android.tv.btservices.settings.SliceBroadcastReceiver.backAndUpdateSliceIntent;
import static com.google.android.tv.btservices.settings.SliceBroadcastReceiver.updateSliceIntent;
import static com.google.android.tv.btservices.settings.SlicesUtil.GENERAL_SLICE_URI;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice;
import androidx.slice.SliceProvider;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.tv.twopanelsettings.slices.builders.PreferenceSliceBuilder;
import com.android.tv.twopanelsettings.slices.builders.PreferenceSliceBuilder.RowBuilder;
import com.google.android.tv.btservices.BluetoothDeviceService;
import com.google.android.tv.btservices.BluetoothUtils;
import com.google.android.tv.btservices.Configuration;
import com.google.android.tv.btservices.PowerUtils;
import com.google.android.tv.btservices.R;
import com.google.android.tv.btservices.SettingsUtils;
import com.google.android.tv.btservices.remote.Version;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Use slice to provide TvSettings "connected devices" information.
*/
public class ConnectedDevicesSliceProvider extends SliceProvider implements
BluetoothDeviceProvider.Listener, ConnectedDevicesPreferenceFragment.Provider {
public static final String ACTION_TOGGLE_CHANGED =
"com.google.android.settings.usage.TOGGLE_CHANGED";
private static final String TAG = "Atv.ConDevsSliceProvider";
private static final boolean DEBUG = false;
private boolean mBtDeviceServiceBound;
private final Map<String, Version> mVersionsMap = new ConcurrentHashMap<>();
private BluetoothDeviceService.LocalBinder mBtDeviceServiceBinder;
private final Map<Uri, Integer> pinnedUris = new ArrayMap<>();
static final String KEY_EXTRAS_DEVICE = "key_extras_device";
private static final String SCHEME_CONTENT = "content://";
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final ServiceConnection mBtDeviceServiceConnection =
new com.google.android.tv.btservices.SimplifiedConnection() {
@Override
protected void cleanUp() {
if (mBtDeviceServiceBinder != null) {
mBtDeviceServiceBinder.removeListener(ConnectedDevicesSliceProvider.this);
}
mBtDeviceServiceBinder = null;
}
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
mBtDeviceServiceBinder = (BluetoothDeviceService.LocalBinder) service;
mBtDeviceServiceBinder.addListener(ConnectedDevicesSliceProvider.this);
getContext().getContentResolver().notifyChange(SlicesUtil.GENERAL_SLICE_URI,
null);
}
};
// BluetoothDeviceProvider.Listener implementation
@Override
public void onDeviceUpdated(BluetoothDevice device) {
getContext().getContentResolver().notifyChange(SlicesUtil.GENERAL_SLICE_URI, null);
getContext().getContentResolver().notifyChange(
SlicesUtil.getDeviceUri(device.getAddress()), null);
updateVersionAndNotify(device);
}
private void updateVersionAndNotify(BluetoothDevice device) {
String addr = device.getAddress();
mHandler.post(() -> {
mVersionsMap.put(addr, getLocalBluetoothDeviceProvider().getVersion(device));
if (device != null) {
getContext().getContentResolver().notifyChange(
SlicesUtil.getDeviceUri(addr), null);
}
});
}
@Override
public boolean isCecEnabled() {
return PowerUtils.isCecControlEnabled(getContext());
}
public List<BluetoothDevice> getBluetoothDevices() {
if (mBtDeviceServiceBinder != null) {
return mBtDeviceServiceBinder.getDevices();
}
return new ArrayList<>();
}
@Override
public BluetoothDeviceProvider getBluetoothDeviceProvider() {
return mBtDeviceServiceBinder;
}
public BluetoothDeviceProvider getLocalBluetoothDeviceProvider() {
return mLocalBluetoothDeviceProvider;
}
@Override
public void onSlicePinned(Uri sliceUri) {
mHandler.post(() -> {
if (DEBUG) {
Log.d(TAG, "Slice pinned: " + sliceUri);
}
Context context = getContext();
if (!mBtDeviceServiceBound && context.bindService(
new Intent(context, BluetoothUtils.getBluetoothDeviceServiceClass(context)),
mBtDeviceServiceConnection, Context.BIND_AUTO_CREATE)) {
mBtDeviceServiceBound = true;
}
if (!pinnedUris.containsKey(sliceUri)) {
pinnedUris.put(sliceUri, 0);
}
pinnedUris.put(sliceUri, pinnedUris.get(sliceUri) + 1);
});
}
@Override
public void onSliceUnpinned(Uri sliceUri) {
mHandler.post(() -> {
if (DEBUG) {
Log.d(TAG, "Slice unpinned: " + sliceUri);
}
Context context = getContext();
// If at this point there is only one slice pinned, we need to unbind the service as
// there won't be any slice pinned after handleSliceUnpinned is called.
if (pinnedUris.containsKey(sliceUri)) {
int newCount = pinnedUris.get(sliceUri) - 1;
pinnedUris.put(sliceUri, newCount);
if (newCount == 0) {
pinnedUris.remove(sliceUri);
}
}
if (pinnedUris.isEmpty() && mBtDeviceServiceBound) {
context.unbindService(mBtDeviceServiceConnection);
mBtDeviceServiceBound = false;
}
});
}
@Override
public boolean onCreateSliceProvider() {
return true;
}
private String getString(int id) {
return getContext().getString(id);
}
@Override
public Slice onBindSlice(Uri sliceUri) {
if (DEBUG) {
Log.d(TAG, "onBindSlice: " + sliceUri);
}
if (SlicesUtil.isGeneralPath(sliceUri)) {
return createGeneralSlice(sliceUri);
} else if (SlicesUtil.isBluetoothDevicePath(sliceUri)) {
return createBluetoothDeviceSlice(sliceUri);
} else if (SlicesUtil.isCecPath(sliceUri)) {
return createCecSlice(sliceUri);
}
return null;
}
@Override
public PendingIntent onCreatePermissionRequest(Uri sliceUri, String callingPackage) {
final Intent settingsIntent = new Intent(Settings.ACTION_SETTINGS);
final PendingIntent noOpIntent = PendingIntent.getActivity(getContext(), 0,
settingsIntent, PendingIntent.FLAG_IMMUTABLE);
return noOpIntent;
}
// The initial slice in the Connected Device flow.
private Slice createGeneralSlice(Uri sliceUri) {
PreferenceSliceBuilder psb = new PreferenceSliceBuilder(getContext(), sliceUri);
psb.addScreenTitle(
new RowBuilder()
.setTitle(getString(R.string.connected_devices_slice_pref_title))
.setPageId(0x18000000)); // TvSettingsEnums.CONNECTED_SLICE
RestrictedLockUtils.EnforcedAdmin admin =
RestrictedLockUtilsInternal.checkIfRestrictionEnforced(getContext(),
UserManager.DISALLOW_CONFIG_BLUETOOTH, UserHandle.myUserId());
PendingIntent pendingIntent;
List<String> updatedUris = Arrays.asList(GENERAL_SLICE_URI.toString());
PendingIntent updateGeneralSliceIntent = updateSliceIntent(getContext(), 0,
new ArrayList<>(updatedUris), GENERAL_SLICE_URI.toString());
if (admin == null) {
Intent i = SettingsUtils.getPairingIntent();
i.putExtra(EXTRA_SLICE_FOLLOWUP, updateGeneralSliceIntent);
pendingIntent = PendingIntent.getActivity(getContext(), 0, i,
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
} else {
Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(getContext(),
admin);
intent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION,
UserManager.DISALLOW_CONFIG_BLUETOOTH)
.putExtra(EXTRA_SLICE_FOLLOWUP, updateGeneralSliceIntent);
pendingIntent = PendingIntent.getActivity(getContext(), 0, intent,
PendingIntent.FLAG_IMMUTABLE);
}
psb.addPreference(new RowBuilder()
.setKey(KEY_PAIR_REMOTE)
.setTitle(getString(R.string.settings_pair_remote))
.setActionId(0x18100000) // TvSettingsEnums.CONNECTED_SLICE_CONNECT_NEW_DEVICES
.setIcon(IconCompat.createWithResource(getContext(),
R.drawable.ic_baseline_add_24dp))
.setIconNeedsToBeProcessed(true)
.setPendingIntent(pendingIntent)
);
// Visually, the connected devices concept covers two categories:
// 1. Any connected devices except the official remote controls. Actively connected devices
// are ranked higher.
// 2. Official remote controls (if present) and their button settings.
updateConnectedDevices(psb);
updateDeviceControl(psb);
return psb.build();
}
private void updateConnectedDevices(PreferenceSliceBuilder psb) {
// Overall BT devices maps
HashMap<String, BluetoothDevice> addressToDevice = new HashMap<>();
// Sets for BT devices that are not official remotes:
// - activeAccessories: they are considered connected from both BluetoothDevice and
// CachedBluetoothDevice's perceptive.
// - inactiveAccessories: they are considered connected from BluetoothDevice's perceptive
// but disconnected from CachedBluetoothDevice's perceptive. They can be easily
// reconnected.
// - bondedAccessories: they are considered merely bonded but not connected from
// BluetoothDevice's perceptive.
Set<String> activeAccessories = new HashSet<>();
Set<String> inactiveAccessories = new HashSet<>();
Set<String> bondedAccessories = new HashSet<>();
// Sets for official BT remotes
// - activeOfficialRemotes: they are considered connected from both BluetoothDevice and
// CachedBluetoothDevice's perceptive.
// - inactiveOfficialRemotes: they are official remotes in states corresponding to the union
// of inactiveAccessories and bondedAccessories.
Set<String> activeOfficialRemotes = new HashSet<>();
Set<String> inactiveOfficialRemotes = new HashSet<>();
// Bucketing all BT devices
for (BluetoothDevice device : getBluetoothDevices()) {
CachedBluetoothDevice cachedDevice =
BluetoothUtils.getCachedBluetoothDevice(getContext(), device);
if (BluetoothUtils.isConnected(device)) {
addressToDevice.put(device.getAddress(), device);
if (cachedDevice != null && cachedDevice.isConnected()) {
if (BluetoothUtils.isOfficialRemote(getContext(), device)) {
activeOfficialRemotes.add(device.getAddress());
} else {
activeAccessories.add(device.getAddress());
}
} else {
if (BluetoothUtils.isOfficialRemote(getContext(), device)) {
inactiveOfficialRemotes.add(device.getAddress());
} else {
inactiveAccessories.add(device.getAddress());
}
}
} else if (BluetoothUtils.isBonded(device)) {
addressToDevice.put(device.getAddress(), device);
if (BluetoothUtils.isOfficialRemote(getContext(), device)) {
inactiveOfficialRemotes.add(device.getAddress());
} else {
bondedAccessories.add(device.getAddress());
}
}
}
// "Accessories" category
if (activeAccessories.size() + inactiveAccessories.size() + bondedAccessories.size()
> 0) {
psb.addPreferenceCategory(new RowBuilder()
.setTitle(getContext().getString(R.string.settings_known_devices_category))
.setKey(KEY_ACCESSORIES));
// Add accessories following the ranking of: active, inactive, bonded.
createAndAddBtDeviceSlicePreferenceFromSet(psb, activeAccessories, addressToDevice);
createAndAddBtDeviceSlicePreferenceFromSet(psb, inactiveAccessories, addressToDevice);
createAndAddBtDeviceSlicePreferenceFromSet(psb, bondedAccessories, addressToDevice);
}
// "Official remote" category
if (activeOfficialRemotes.size() + inactiveOfficialRemotes.size() > 0) {
psb.addPreferenceCategory(new RowBuilder()
.setTitle(getContext().getString(R.string.settings_official_remote_category))
.setKey(KEY_OFFICIAL_REMOTES));
createAndAddBtDeviceSlicePreferenceFromSet(psb, activeOfficialRemotes, addressToDevice);
createAndAddBtDeviceSlicePreferenceFromSet(
psb, inactiveOfficialRemotes, addressToDevice);
}
// Adding the remote buttons settings at the bottom
updateAxel(psb);
}
private void updateDeviceControl(PreferenceSliceBuilder psb) {
// Currently, HDMI-CEC settings is the only entry within the device control category so we
// should hide the whole category including the header if HDMI-CEC settings is not visible.
if (!ConnectedDevicesPreferenceFragment.isCecSettingsEnabled(getContext())
|| !showCecInConnectedSettings()) {
return;
}
RowBuilder category = new RowBuilder().setKey(KEY_DEVICE_CONTROL)
.setTitle(getString(R.string.settings_devices_control));
psb.addPreferenceCategory(category);
updateCecSettings(psb);
}
private void updateAxel(PreferenceSliceBuilder psb) {
if (!ConnectedDevicesPreferenceFragment.isAxelSettingsEnabled(getContext())) {
return;
}
RowBuilder axelPref = new RowBuilder()
.setKey(KEY_AXEL_TOGGLE)
.setTitle(getString(R.string.settings_axel))
.setSubtitle(getString(R.string.settings_axel_description))
.setTargetSliceUri(SlicesUtil.AXEL_SLICE_URI.toString());
psb.addPreference(axelPref);
}
private void updateCecSettings(PreferenceSliceBuilder psb) {
if (!ConnectedDevicesPreferenceFragment.isCecSettingsEnabled(getContext())
|| !showCecInConnectedSettings()) {
return;
}
RowBuilder cecPref = new RowBuilder()
.setKey(KEY_CEC_TOGGLE)
.setTitle(getString(R.string.settings_hdmi_cec))
.setSubtitle(isCecEnabled() ? getString(R.string.settings_enabled)
: getString(R.string.settings_disabled))
.setTargetSliceUri(SlicesUtil.CEC_SLICE_URI.toString());
psb.addPreference(cecPref);
}
// The slice page that shows detail information of a particular device.
private Slice createBluetoothDeviceSlice(Uri sliceUri) {
Context context = getContext();
String deviceAddr = SlicesUtil.getDeviceAddr(sliceUri);
BluetoothDevice device = BluetoothDeviceService
.findDevice(SlicesUtil.getDeviceAddr(sliceUri));
CachedBluetoothDevice cachedDevice =
BluetoothUtils.getCachedBluetoothDevice(getContext(), device);
String deviceName = "";
if (device != null) {
deviceName = BluetoothUtils.getName(device);
}
PreferenceSliceBuilder psb = new PreferenceSliceBuilder(getContext(), sliceUri);
psb.addScreenTitle(
new RowBuilder()
.setTitle(deviceName)
.setPageId(0x18200000)); // TvSettingsEnums.CONNECTED_SLICE_DEVICE_ENTRY
Bundle extras = new Bundle();
Intent i = null;
// Update "update preference".
if (BluetoothUtils.isRemote(context, device)) {
i = new Intent(context, ResponseActivity.class);
RowBuilder updatePref = new RowBuilder().setKey(KEY_UPDATE);
ResponseFragment.prepareArgs(
extras,
KEY_UPDATE,
R.string.settings_bt_update,
R.string.settings_bt_update_summary,
0,
CONT_CANCEL_ARGS,
null,
ResponseFragment.DEFAULT_CHOICE_UNDEFINED
);
i.putExtras(extras).putExtra(KEY_EXTRAS_DEVICE, device)
.setData(Uri.parse(SCHEME_CONTENT + device.getAddress()));
List<String> updatedUris = Arrays.asList(GENERAL_SLICE_URI.toString(),
sliceUri.toString());
PendingIntent updateSliceIntent = updateSliceIntent(getContext(), 0,
new ArrayList<>(updatedUris), sliceUri.toString());
i.putExtra(EXTRA_SLICE_FOLLOWUP, updateSliceIntent);
PendingIntent updatePendingIntent =
PendingIntent.getActivity(context, 0, i,
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
updatePref.setPendingIntent(updatePendingIntent);
BluetoothDeviceProvider btDeviceProvider = getLocalBluetoothDeviceProvider();
if (btDeviceProvider.hasUpgrade(device)) {
updatePref.setTitle(getString(R.string.settings_bt_update));
updatePref.setEnabled(true);
updatePref.setSelectable(true);
// TvSettingsEnums.CONNECTED_SLICE_DEVICE_ENTRY_UPDATE
updatePref.setActionId(0x18210000);
if (btDeviceProvider.isBatteryLow(device)) {
updatePref.setSubtitle(getString(R.string.settings_bt_battery_low));
updatePref.setEnabled(false);
updatePref.setSelectable(false);
} else {
updatePref.setSubtitle(
getString(R.string.settings_bt_update_software_available));
}
} else {
updatePref.setTitle(getString(R.string.settings_bt_update_not_necessary));
updatePref.setSubtitle(null);
updatePref.setEnabled(false);
updatePref.setSelectable(false);
}
psb.addPreference(updatePref);
}
// Update "connect/disconnect preference"
if (showConnectDisconnectButtons(device) && cachedDevice != null
&& !cachedDevice.isBusy()) {
// Whether the device is actually connected from CachedBluetoothDevice's perceptive.
boolean isConnected = BluetoothUtils.isConnected(device) && cachedDevice.isConnected();
RowBuilder disconnectPref = new RowBuilder()
.setKey(isConnected ? KEY_DISCONNECT : KEY_CONNECT)
.setTitle(getString(isConnected
? R.string.bluetooth_disconnect : R.string.bluetooth_connect));
extras = new Bundle();
i = new Intent(context, ResponseActivity.class);
ResponseFragment.prepareArgs(
extras,
isConnected ? KEY_DISCONNECT : KEY_CONNECT,
isConnected ? R.string.settings_bt_disconnect : R.string.settings_bt_connect,
0,
R.drawable.ic_baseline_bluetooth_searching_large,
YES_NO_ARGS,
deviceName,
isConnected ? 1 /* default to NO (index 1) */ : 0 /* default to YES */
);
i.putExtras(extras)
.putExtra(KEY_EXTRAS_DEVICE, device)
.setData(Uri.parse(SCHEME_CONTENT + device.getAddress()));
List<String> updatedUris = Arrays.asList(GENERAL_SLICE_URI.toString(),
sliceUri.toString());
PendingIntent updateSliceIntent = backAndUpdateSliceIntent(getContext(), 1,
new ArrayList<>(updatedUris), sliceUri.toString());
i.putExtra(EXTRA_SLICE_FOLLOWUP, updateSliceIntent);
PendingIntent pendingIntent = PendingIntent
.getActivity(context, 1, i,
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
disconnectPref.setPendingIntent(pendingIntent);
psb.addPreference(disconnectPref);
}
// Update "rename preference".
RowBuilder renamePref = new RowBuilder()
.setKey(KEY_RENAME)
.setTitle(getString(R.string.bluetooth_rename))
.setActionId(0x18220000); // TvSettingsEnums.CONNECTED_SLICE_DEVICE_ENTRY_RENAME
extras = new Bundle();
ResponseFragment.prepareArgs(
extras,
KEY_RENAME,
R.string.settings_bt_rename,
0,
R.drawable.ic_baseline_bluetooth_searching_large,
null,
deviceName,
ResponseFragment.DEFAULT_CHOICE_UNDEFINED
);
i = new Intent(context, ResponseActivity.class)
.putExtra(KEY_EXTRAS_DEVICE, device)
.putExtras(extras)
.setData(Uri.parse(SCHEME_CONTENT + device.getAddress()));
List<String> updatedUris = Arrays.asList(GENERAL_SLICE_URI.toString(), sliceUri.toString());
PendingIntent updateSliceIntent = updateSliceIntent(getContext(), 2,
new ArrayList<>(updatedUris), sliceUri.toString());
i.putExtra(EXTRA_SLICE_FOLLOWUP, updateSliceIntent);
PendingIntent renamePendingIntent = PendingIntent
.getActivity(context, 2, i,
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
renamePref.setPendingIntent(renamePendingIntent);
psb.addPreference(renamePref);
// Update "forget preference".
RowBuilder forgetPref = new RowBuilder()
.setKey(KEY_FORGET)
.setTitle(getString(R.string.bluetooth_forget))
.setActionId(0x18230000); // TvSettingsEnums.CONNECTED_SLICE_DEVICE_ENTRY_FORGET
extras = new Bundle();
i = new Intent(context, ResponseActivity.class);
ResponseFragment.prepareArgs(
extras,
KEY_FORGET,
R.string.settings_bt_forget,
0,
R.drawable.ic_baseline_bluetooth_searching_large,
YES_NO_ARGS,
deviceName,
1 /* default to NO (index 1) */
);
i.putExtras(extras).putExtra(KEY_EXTRAS_DEVICE, device)
.setData(Uri.parse(SCHEME_CONTENT + device.getAddress()));
updatedUris = Arrays.asList(GENERAL_SLICE_URI.toString(), sliceUri.toString());
updateSliceIntent = backAndUpdateSliceIntent(getContext(), 3,
new ArrayList<>(updatedUris), sliceUri.toString());
i.putExtra(EXTRA_SLICE_FOLLOWUP, updateSliceIntent);
PendingIntent disconnectPendingIntent = PendingIntent
.getActivity(context, 3, i,
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
forgetPref.setPendingIntent(disconnectPendingIntent);
psb.addPreference(forgetPref);
// Update "bluetooth device info preference".
RowBuilder infoPref = new RowBuilder()
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_baseline_info_24dp));
BluetoothDeviceProvider provider = getLocalBluetoothDeviceProvider();
int battery = provider.getBatteryLevel(device);
if (battery != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
String batteryText = provider.mapBatteryLevel(context, device, battery);
final String warning = getString(R.string.settings_bt_battery_low);
if (provider.isBatteryLow(device) && !provider.hasUpgrade(device)) {
batteryText = context.getString(
R.string.settings_remote_battery_level_with_warning_label, batteryText,
warning);
}
infoPref.addInfoItem(getString(R.string.settings_remote_battery_level_label),
batteryText);
}
if (mVersionsMap.containsKey(deviceAddr)) {
Version version = mVersionsMap.get(deviceAddr);
if (!Version.BAD_VERSION.equals(version)) {
infoPref.addInfoItem(getString(R.string.settings_remote_firmware_label),
version.toVersionString());
}
infoPref.addInfoItem(getString(R.string.settings_remote_serial_number_label),
deviceAddr);
psb.addPreference(infoPref);
}
return psb.build();
}
// The slice that shows CEC control related information
private Slice createCecSlice(Uri sliceUri) {
Context context = getContext();
PreferenceSliceBuilder psb = new PreferenceSliceBuilder(context, sliceUri);
psb.addScreenTitle(
new RowBuilder()
.setTitle(getString(R.string.settings_hdmi_cec))
.setPageId(0x18300000)); // TvSettingsEnums.CONNECTED_SLICE_HDMICEC
final boolean isEnabled = PowerUtils.isCecControlEnabled(getContext());
Intent intent = new Intent(ACTION_TOGGLE_CHANGED);
intent.putExtra(TOGGLE_TYPE, CEC);
intent.putExtra(TOGGLE_STATE, !isEnabled);
intent.setClass(context, SliceBroadcastReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getContext(), 0, intent,
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
psb.addPreference(new RowBuilder()
.setTitle(getString(R.string.settings_enable_hdmi_cec))
.setActionId(0x18310000) // TvSettingsEnums.CONNECTED_SLICE_HDMICEC_ON_OFF
.addSwitch(pendingIntent, null, isEnabled));
psb.addPreference(new RowBuilder()
.setTitle(getString(R.string.settings_cec_explain))
.setEnabled(false));
psb.addPreference(new RowBuilder()
.setTitle(getString(R.string.settings_cec_feature_names))
.setEnabled(false));
return psb.build();
}
private void createAndAddBtDeviceSlicePreferenceFromSet(
PreferenceSliceBuilder psb,
Set<String> addresses,
HashMap<String, BluetoothDevice> addressesToBtDeviceMap) {
if (psb == null || addresses == null || addresses.isEmpty()
|| addressesToBtDeviceMap == null || addressesToBtDeviceMap.isEmpty()) {
return;
}
final List<String> devicesAddressesList = new ArrayList<>(addresses);
Collections.sort(devicesAddressesList);
for (String deviceAddr : devicesAddressesList) {
psb.addPreference(
createBtDeviceSlicePreference(
getContext(),
getBluetoothDeviceProvider(),
addressesToBtDeviceMap.get(deviceAddr)));
}
}
private static PreferenceSliceBuilder.RowBuilder createBtDeviceSlicePreference(
Context context, BluetoothDeviceProvider provider, BluetoothDevice device) {
PreferenceSliceBuilder.RowBuilder pref = new PreferenceSliceBuilder.RowBuilder();
pref.setKey(device.getAddress());
updateBtDevicePreference(context, provider, device, pref);
RestrictedLockUtils.EnforcedAdmin admin =
RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context,
UserManager.DISALLOW_CONFIG_BLUETOOTH, UserHandle.myUserId());
if (admin == null) {
Uri targetSliceUri = SlicesUtil.getDeviceUri(device.getAddress());
pref.setTargetSliceUri(targetSliceUri.toString());
} else {
Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(context, admin);
intent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION,
UserManager.DISALLOW_CONFIG_BLUETOOTH);
pref.setPendingIntent(PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_IMMUTABLE));
}
return pref;
}
private static void updateBtDevicePreference(Context context, BluetoothDeviceProvider provider,
BluetoothDevice device, PreferenceSliceBuilder.RowBuilder pref) {
int batteryLevel = provider.getBatteryLevel(device);
pref.setKey(device.getAddress());
pref.setTitle(BluetoothUtils.getName(device));
if (provider.hasUpgrade(device)) {
pref.setSubtitle(context.getString(R.string.settings_bt_update_available));
} else {
if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
if (provider.isBatteryLow(device)) {
pref.setSubtitle(context.getString(R.string.settings_bt_battery_low_warning));
} else {
String batteryText = provider.mapBatteryLevel(context, device, batteryLevel);
pref.setSubtitle(context.getString(R.string.settings_remote_battery_level,
batteryText));
}
} else {
boolean isConnected = BluetoothUtils.isConnected(device)
&& BluetoothUtils.getCachedBluetoothDevice(context, device) != null
&& BluetoothUtils.getCachedBluetoothDevice(context, device).isConnected();
pref.setSubtitle(isConnected
? context.getString(R.string.bluetooth_connected_status)
: context.getString(R.string.bluetooth_disconnected_status));
}
}
pref.setIcon(IconCompat.createWithResource(
context, BluetoothUtils.getIcon(context, device)));
pref.setIconNeedsToBeProcessed(true);
}
private final BluetoothDeviceProvider mLocalBluetoothDeviceProvider =
new LocalBluetoothDeviceProvider() {
final BluetoothDeviceProvider getHostBluetoothDeviceProvider() {
return getBluetoothDeviceProvider();
}
};
private boolean showCecInConnectedSettings() {
return Configuration.get(getContext()).isEnabled(R.bool.show_cec_in_connected_settings);
}
private boolean showConnectDisconnectButtons(BluetoothDevice device) {
return !BluetoothUtils.isBluetoothDeviceMetadataInList(
getContext(),
device,
BluetoothDevice.METADATA_MODEL_NAME,
R.array.disconnect_button_hidden_device_model_names);
}
}