Files
app_Settings/src/com/android/settings/bluetooth/BluetoothFindBroadcastsFragment.java
SongFerngWang e8bb9c9be3 [BT Broadcast Sink] Add the function for leave and scan QRcode buttons
Add function for 'leave broadcast' button
Add function for 'Scan QR code' button
Set 'level broadcast' button gray out when device does not have broadcast source

Bug: 228259065
Test: build pass and manually test
Change-Id: Iab4a45e73f49c3f755b95ea3fa38872daac7e745
2022-05-05 18:16:44 +08:00

403 lines
15 KiB
Java

/*
* Copyright (C) 2022 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.bluetooth;
import static android.bluetooth.BluetoothDevice.BOND_NONE;
import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
import android.app.AlertDialog;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.bluetooth.le.ScanFilter;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import com.android.settings.R;
import com.android.settings.dashboard.RestrictedDashboardFragment;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* This fragment allowed users to find the nearby broadcast sources.
*/
public class BluetoothFindBroadcastsFragment extends RestrictedDashboardFragment {
private static final String TAG = "BtFindBroadcastsFrg";
public static final String KEY_DEVICE_ADDRESS = "device_address";
public static final String PREF_KEY_BROADCAST_SOURCE_LIST = "broadcast_source_list";
@VisibleForTesting
String mDeviceAddress;
@VisibleForTesting
LocalBluetoothManager mManager;
@VisibleForTesting
CachedBluetoothDevice mCachedDevice;
@VisibleForTesting
PreferenceCategory mBroadcastSourceListCategory;
BluetoothFindBroadcastsHeaderController mBluetoothFindBroadcastsHeaderController;
private LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
private BluetoothBroadcastSourcePreference mSelectedPreference;
private Executor mExecutor;
private int mSourceId;
private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
new BluetoothLeBroadcastAssistant.Callback() {
@Override
public void onSearchStarted(int reason) {
Log.d(TAG, "onSearchStarted: " + reason);
getActivity().runOnUiThread(
() -> cacheRemoveAllPrefs(mBroadcastSourceListCategory));
}
@Override
public void onSearchStartFailed(int reason) {
Log.d(TAG, "onSearchStartFailed: " + reason);
}
@Override
public void onSearchStopped(int reason) {
Log.d(TAG, "onSearchStopped: " + reason);
}
@Override
public void onSearchStopFailed(int reason) {
Log.d(TAG, "onSearchStopFailed: " + reason);
}
@Override
public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {
Log.d(TAG, "onSourceFound:");
getActivity().runOnUiThread(() -> updateListCategory(source, false));
}
@Override
public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
setSourceId(sourceId);
if (mSelectedPreference == null) {
Log.w(TAG, "onSourceAdded: mSelectedPreference == null!");
return;
}
getActivity().runOnUiThread(() -> updateListCategory(
mSelectedPreference.getBluetoothLeBroadcastMetadata(), true));
}
@Override
public void onSourceAddFailed(@NonNull BluetoothDevice sink,
@NonNull BluetoothLeBroadcastMetadata source, int reason) {
mSelectedPreference = null;
Log.d(TAG, "onSourceAddFailed: clear the mSelectedPreference.");
}
@Override
public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId,
int reason) {
}
@Override
public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId,
int reason) {
}
@Override
public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId,
int reason) {
Log.d(TAG, "onSourceRemoved:");
}
@Override
public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId,
int reason) {
Log.d(TAG, "onSourceRemoveFailed:");
}
@Override
public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId,
@NonNull BluetoothLeBroadcastReceiveState state) {
Log.d(TAG, "onReceiveStateChanged:");
}
};
public BluetoothFindBroadcastsFragment() {
super(DISALLOW_CONFIG_BLUETOOTH);
}
@VisibleForTesting
LocalBluetoothManager getLocalBluetoothManager(Context context) {
return Utils.getLocalBtManager(context);
}
@VisibleForTesting
CachedBluetoothDevice getCachedDevice(String deviceAddress) {
BluetoothDevice remoteDevice =
mManager.getBluetoothAdapter().getRemoteDevice(deviceAddress);
return mManager.getCachedDeviceManager().findDevice(remoteDevice);
}
@Override
public void onAttach(Context context) {
mDeviceAddress = getArguments().getString(KEY_DEVICE_ADDRESS);
mManager = getLocalBluetoothManager(context);
mCachedDevice = getCachedDevice(mDeviceAddress);
mLeBroadcastAssistant = getLeBroadcastAssistant();
mExecutor = Executors.newSingleThreadExecutor();
super.onAttach(context);
if (mCachedDevice == null || mLeBroadcastAssistant == null) {
//Close this page if device is null with invalid device mac address
//or if the device does not have LeBroadcastAssistant profile
Log.w(TAG, "onAttach() CachedDevice or LeBroadcastAssistant is null!");
finish();
return;
}
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mBroadcastSourceListCategory = findPreference(PREF_KEY_BROADCAST_SOURCE_LIST);
}
@Override
public void onStart() {
super.onStart();
if (mLeBroadcastAssistant != null) {
mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
}
}
@Override
public void onResume() {
super.onResume();
finishFragmentIfNecessary();
//check assistant status. Start searching...
if (mLeBroadcastAssistant != null && !mLeBroadcastAssistant.isSearchInProgress()) {
mLeBroadcastAssistant.startSearchingForSources(getScanFilter());
}
}
@Override
public void onStop() {
super.onStop();
if (mLeBroadcastAssistant != null) {
mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
}
}
@VisibleForTesting
void finishFragmentIfNecessary() {
if (mCachedDevice.getBondState() == BOND_NONE) {
finish();
return;
}
}
@Override
public int getMetricsCategory() {
//TODO(b/228255796) : add new enum for find broadcast fragment
return SettingsEnums.PAGE_UNKNOWN;
}
/**
* Starts to scan broadcast source by the BluetoothLeBroadcastAssistant.
*/
public void scanBroadcastSource() {
if (mLeBroadcastAssistant == null) {
Log.w(TAG, "scanBroadcastSource: LeBroadcastAssistant is null!");
return;
}
mLeBroadcastAssistant.startSearchingForSources(getScanFilter());
}
/**
* Leaves the broadcast source by the BluetoothLeBroadcastAssistant.
*/
public void leaveBroadcastSession() {
if (mLeBroadcastAssistant == null || mCachedDevice == null) {
Log.w(TAG, "leaveBroadcastSession: LeBroadcastAssistant or CachedDevice is null!");
return;
}
mLeBroadcastAssistant.removeSource(mCachedDevice.getDevice(), getSourceId());
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.bluetooth_find_broadcasts_fragment;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
ArrayList<AbstractPreferenceController> controllers = new ArrayList<>();
if (mCachedDevice != null) {
Lifecycle lifecycle = getSettingsLifecycle();
mBluetoothFindBroadcastsHeaderController = new BluetoothFindBroadcastsHeaderController(
context, this, mCachedDevice, lifecycle, mManager);
controllers.add(mBluetoothFindBroadcastsHeaderController);
}
return controllers;
}
/**
* Gets the LocalBluetoothLeBroadcastAssistant
* @return the LocalBluetoothLeBroadcastAssistant
*/
public LocalBluetoothLeBroadcastAssistant getLeBroadcastAssistant() {
if (mManager == null) {
Log.w(TAG, "getLeBroadcastAssistant: LocalBluetoothManager is null!");
return null;
}
LocalBluetoothProfileManager profileManager = mManager.getProfileManager();
if (profileManager == null) {
Log.w(TAG, "getLeBroadcastAssistant: LocalBluetoothProfileManager is null!");
return null;
}
return profileManager.getLeAudioBroadcastAssistantProfile();
}
private List<ScanFilter> getScanFilter() {
// Currently there is no function for setting the ScanFilter. It may have this function
// in the further.
return Collections.emptyList();
}
private void updateListCategory(BluetoothLeBroadcastMetadata source, boolean isConnected) {
BluetoothBroadcastSourcePreference item = mBroadcastSourceListCategory.findPreference(
Integer.toString(source.getBroadcastId()));
if (item == null) {
item = createBluetoothBroadcastSourcePreference(source);
mBroadcastSourceListCategory.addPreference(item);
}
item.updateMetadataAndRefreshUi(source, isConnected);
item.setOrder(isConnected ? 0 : 1);
//refresh the header
if (mBluetoothFindBroadcastsHeaderController != null) {
mBluetoothFindBroadcastsHeaderController.refreshUi();
}
}
private BluetoothBroadcastSourcePreference createBluetoothBroadcastSourcePreference(
BluetoothLeBroadcastMetadata source) {
BluetoothBroadcastSourcePreference pref = new BluetoothBroadcastSourcePreference(
getContext(), source);
pref.setKey(Integer.toString(source.getBroadcastId()));
pref.setOnPreferenceClickListener(preference -> {
if (source.isEncrypted()) {
launchBroadcastCodeDialog(pref);
} else {
addSource(pref);
}
return true;
});
return pref;
}
private void addSource(BluetoothBroadcastSourcePreference pref) {
if (mLeBroadcastAssistant == null || mCachedDevice == null) {
Log.w(TAG, "addSource: LeBroadcastAssistant or CachedDevice is null!");
return;
}
if (mSelectedPreference != null) {
// The previous preference status set false after user selects the new Preference.
getActivity().runOnUiThread(
() -> {
mSelectedPreference.updateMetadataAndRefreshUi(
mSelectedPreference.getBluetoothLeBroadcastMetadata(), false);
mSelectedPreference.setOrder(1);
});
}
mSelectedPreference = pref;
mLeBroadcastAssistant.addSource(mCachedDevice.getDevice(),
pref.getBluetoothLeBroadcastMetadata(), true);
}
private void addBroadcastCodeIntoPreference(BluetoothBroadcastSourcePreference pref,
String broadcastCode) {
BluetoothLeBroadcastMetadata metadata =
new BluetoothLeBroadcastMetadata.Builder(pref.getBluetoothLeBroadcastMetadata())
.setBroadcastCode(broadcastCode.getBytes(StandardCharsets.UTF_8))
.build();
pref.updateMetadataAndRefreshUi(metadata, false);
}
private void launchBroadcastCodeDialog(BluetoothBroadcastSourcePreference pref) {
final View layout = LayoutInflater.from(getContext()).inflate(
R.layout.bluetooth_find_broadcast_password_dialog, null);
final TextView broadcastName = layout.requireViewById(R.id.broadcast_name_text);
final EditText editText = layout.requireViewById(R.id.broadcast_edit_text);
broadcastName.setText(pref.getTitle());
AlertDialog alertDialog = new AlertDialog.Builder(getContext())
.setTitle(R.string.find_broadcast_password_dialog_title)
.setView(layout)
.setNeutralButton(android.R.string.cancel, null)
.setPositiveButton(R.string.bluetooth_connect_access_dialog_positive,
(d, w) -> {
Log.d(TAG, "setPositiveButton: clicked");
addBroadcastCodeIntoPreference(pref, editText.getText().toString());
addSource(pref);
})
.create();
alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
alertDialog.show();
}
public int getSourceId() {
return mSourceId;
}
public void setSourceId(int sourceId) {
mSourceId = sourceId;
}
}