diff --git a/res/layout/bluetooth_dialog.xml b/res/layout/bluetooth_dialog.xml
new file mode 100644
index 0000000..606c8d5
--- /dev/null
+++ b/res/layout/bluetooth_dialog.xml
@@ -0,0 +1,226 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/bluetooth_list_item.xml b/res/layout/bluetooth_list_item.xml
new file mode 100644
index 0000000..4d47b06
--- /dev/null
+++ b/res/layout/bluetooth_list_item.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values/aospa_strings.xml b/res/values/aospa_strings.xml
index 47ff39e..6b66ea8 100644
--- a/res/values/aospa_strings.xml
+++ b/res/values/aospa_strings.xml
@@ -49,4 +49,9 @@
Battery Share off\nBattery too low
Battery Share is enabled
+
+ Bluetooth
+ Bluetooth is off
+ Tap a device to connect/disconnect
+
diff --git a/src/co/aospa/systemui/qs/tileimpl/ParanoidQSModule.kt b/src/co/aospa/systemui/qs/tileimpl/ParanoidQSModule.kt
index b0cc023..57193b6 100644
--- a/src/co/aospa/systemui/qs/tileimpl/ParanoidQSModule.kt
+++ b/src/co/aospa/systemui/qs/tileimpl/ParanoidQSModule.kt
@@ -19,6 +19,7 @@ package co.aospa.systemui.qs.tileimpl;
import com.android.systemui.qs.tileimpl.QSTileImpl
import co.aospa.systemui.qs.tiles.AlwaysOnDisplayTile;
+import co.aospa.systemui.qs.tiles.BluetoothDialogTile;
import co.aospa.systemui.qs.tiles.CaffeineTile;
import co.aospa.systemui.qs.tiles.DataSwitchTile;
import co.aospa.systemui.qs.tiles.DcDimmingTile;
@@ -38,6 +39,12 @@ interface ParanoidQSModule {
@StringKey(AlwaysOnDisplayTile.TILE_SPEC)
fun bindAlwaysOnDisplayTile(alwaysOnDisplayTile: AlwaysOnDisplayTile): QSTileImpl<*>
+ /** Inject BluetoothDialogTile into tileMap in QSModule */
+ @Binds
+ @IntoMap
+ @StringKey(BluetoothDialogTile.TILE_SPEC)
+ fun bindBluetoothDialogTile(bluetoothDialogTile: BluetoothDialogTile): QSTileImpl<*>
+
/** Inject CaffeineTile into tileMap in QSModule */
@Binds
@IntoMap
diff --git a/src/co/aospa/systemui/qs/tiles/BluetoothDialogTile.java b/src/co/aospa/systemui/qs/tiles/BluetoothDialogTile.java
new file mode 100644
index 0000000..75c5f27
--- /dev/null
+++ b/src/co/aospa/systemui/qs/tiles/BluetoothDialogTile.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 StatiXOS
+ *
+ * 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.
+ *
+ * Changes from Qualcomm Innovation Center are provided under the following license:
+ *
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ */
+
+package co.aospa.systemui.qs.tiles;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.MetricsLogger;
+
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tiles.BluetoothTile;
+import com.android.systemui.statusbar.policy.BluetoothController;
+
+import co.aospa.systemui.qs.tiles.dialog.BluetoothDialogFactory;
+
+import javax.inject.Inject;
+
+public class BluetoothDialogTile extends BluetoothTile {
+
+ public static final String TILE_SPEC = "bt";
+
+ private final Handler mHandler;
+ private final BluetoothDialogFactory mBluetoothDialogFactory;
+
+ @Inject
+ public BluetoothDialogTile(
+ QSHost host,
+ @Background Looper backgroundLooper,
+ @Main Handler mainHandler,
+ FalsingManager falsingManager,
+ MetricsLogger metricsLogger,
+ StatusBarStateController statusBarStateController,
+ ActivityStarter activityStarter,
+ QSLogger qsLogger,
+ BluetoothController bluetoothController,
+ BluetoothDialogFactory bluetoothDialogFactory
+ ) {
+ super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ statusBarStateController, activityStarter, qsLogger, bluetoothController);
+ mHandler = mainHandler;
+ mBluetoothDialogFactory = bluetoothDialogFactory;
+ }
+
+ @Override
+ public BooleanState newTileState() {
+ BooleanState s = new BooleanState();
+ s.forceExpandIcon = true;
+ return s;
+ }
+
+ @Override
+ protected void handleClick(@Nullable View view) {
+ mHandler.post(() -> mBluetoothDialogFactory.create(true, view));
+ }
+}
diff --git a/src/co/aospa/systemui/qs/tiles/dialog/BluetoothDialog.java b/src/co/aospa/systemui/qs/tiles/dialog/BluetoothDialog.java
new file mode 100644
index 0000000..3e3754e
--- /dev/null
+++ b/src/co/aospa/systemui/qs/tiles/dialog/BluetoothDialog.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * (C) 2022 Paranoid Android
+ *
+ * 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.
+ *
+ * Changes from Qualcomm Innovation Center are provided under the following license:
+ *
+ * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ */
+
+package co.aospa.systemui.qs.tiles.dialog;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Settings;
+import android.text.Layout;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.settingslib.Utils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.BluetoothController;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Dialog for bluetooth
+ */
+@SysUISingleton
+public class BluetoothDialog extends SystemUIDialog implements Window.Callback {
+ private static final String TAG = "BluetoothDialog";
+ private static final boolean DEBUG = true;
+
+ public static final int MAX_DEVICES_COUNT = 4;
+ private static final String SAVED_DEVICES_INTENT = "android.settings.SAVED_DEVICES";
+
+ private BluetoothViewAdapter mAdapter;
+ private BluetoothController mBluetoothController;
+ private BluetoothDialogFactory mBluetoothDialogFactory;
+ private Context mContext;
+ private Handler mHandler;
+ private View mDialogView;
+ private TextView mBluetoothDialogTitle;
+ private TextView mBluetoothDialogSubTitle;
+ private TextView mBluetoothToggleText;
+ private Switch mBluetoothToggle;
+ private ProgressBar mProgressBar;
+ private View mDivider;
+ private LinearLayout mTurnOnLayout;
+ private LinearLayout mSeeAllLayout;
+ private RecyclerView mBluetoothRecyclerView;
+ private Button mDoneButton;
+ private Button mSettingsButton;
+ private DialogLaunchAnimator mDialogLaunchAnimator;
+ private ActivityStarter mActivityStarter;
+
+ private Drawable mBackgroundOn;
+ private Drawable mBackgroundOff;
+
+ private final BluetoothController.Callback mCallback = new BluetoothController.Callback() {
+ @Override
+ public void onBluetoothStateChange(boolean enabled) {
+ if (DEBUG) {
+ Log.i(TAG, "onBluetoothStateChange enabled=" + enabled);
+ }
+ mBluetoothToggle.setChecked(enabled);
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onBluetoothDevicesChanged() {
+ if (DEBUG) {
+ Log.i(TAG, "onBluetoothDevicesChanged");
+ }
+ mHandler.post(() -> updateDialog());
+ }
+ };
+
+ public BluetoothDialog(Context context, BluetoothDialogFactory bluetoothDialogFactory,
+ boolean aboveStatusBar, @Main Handler handler, ActivityStarter activityStarter,
+ DialogLaunchAnimator dialogLaunchAnimator, BluetoothController bluetoothController) {
+ super(context);
+ if (DEBUG) {
+ Log.d(TAG, "Init BluetoothDialog");
+ }
+
+ // Save the context that is wrapped with our theme.
+ mContext = getContext();
+ mHandler = handler;
+ mBluetoothDialogFactory = bluetoothDialogFactory;
+ mBluetoothController = bluetoothController;
+ mActivityStarter = activityStarter;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
+ mAdapter = new BluetoothViewAdapter(this);
+
+ if (!aboveStatusBar) {
+ getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (DEBUG) {
+ Log.d(TAG, "onCreate");
+ }
+ mDialogView = LayoutInflater.from(mContext).inflate(R.layout.bluetooth_dialog, null);
+ mDialogView.setAccessibilityPaneTitle(
+ mContext.getText(R.string.accessibility_desc_quick_settings));
+ final Window window = getWindow();
+ window.setContentView(mDialogView);
+ window.setWindowAnimations(R.style.Animation_InternetDialog);
+
+ mBluetoothDialogTitle = mDialogView.requireViewById(R.id.bluetooth_dialog_title);
+ mBluetoothDialogSubTitle = mDialogView.requireViewById(R.id.bluetooth_dialog_subtitle);
+ mProgressBar = mDialogView.requireViewById(R.id.bluetooth_progress);
+ mDivider = mDialogView.requireViewById(R.id.divider);
+ mBluetoothToggle = mDialogView.requireViewById(R.id.bluetooth_toggle);
+ mBluetoothToggleText = mDialogView.requireViewById(R.id.bluetooth_toggle_title);
+ mBluetoothRecyclerView = mDialogView.requireViewById(R.id.bluetooth_list_layout);
+ mSeeAllLayout = mDialogView.requireViewById(R.id.see_all_layout);
+ mTurnOnLayout = mDialogView.requireViewById(R.id.turn_on_bluetooth_layout);
+ mDoneButton = mDialogView.requireViewById(R.id.done_button);
+ mSettingsButton = mDialogView.requireViewById(R.id.settings_button);
+ mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
+ mBackgroundOff = mContext.getDrawable(R.drawable.internet_dialog_selected_effect);
+
+ mBluetoothToggle.setOnCheckedChangeListener(
+ (buttonView, isChecked) -> {
+ mBluetoothController.setBluetoothEnabled(isChecked);
+ });
+ mSeeAllLayout.setOnClickListener(v -> {
+ startActivity(new Intent(SAVED_DEVICES_INTENT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), v);
+ });
+ mDoneButton.setOnClickListener(v -> dismissDialog());
+ mSettingsButton.setOnClickListener(v -> {
+ startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), v);
+ });
+ mBluetoothRecyclerView.setLayoutManager(new LinearLayoutManager(mContext));
+ mBluetoothRecyclerView.setAdapter(mAdapter);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (DEBUG) {
+ Log.d(TAG, "onStart");
+ }
+ mBluetoothController.addCallback(mCallback);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (DEBUG) {
+ Log.d(TAG, "onStop");
+ }
+ mBluetoothController.removeCallback(mCallback);
+ mSeeAllLayout.setOnClickListener(null);
+ mBluetoothToggle.setOnCheckedChangeListener(null);
+ mDoneButton.setOnClickListener(null);
+ mSettingsButton.setOnClickListener(null);
+ mBluetoothDialogFactory.destroyDialog();
+ }
+
+ public void dismissDialog() {
+ if (DEBUG) {
+ Log.d(TAG, "dismissDialog");
+ }
+ mBluetoothDialogFactory.destroyDialog();
+ dismiss();
+ }
+
+ void startActivity(Intent intent, View view) {
+ ActivityLaunchAnimator.Controller controller =
+ mDialogLaunchAnimator.createActivityLaunchController(view);
+
+ if (controller == null) {
+ dismissDialog();
+ }
+
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0, controller);
+ }
+
+ /**
+ * Update the bluetooth dialog when receiving the callback.
+ */
+ void updateDialog() {
+ if (DEBUG) {
+ Log.d(TAG, "updateDialog");
+ }
+ // subtitle
+ int subtitle = R.string.bluetooth_is_off;
+ boolean enabled = mBluetoothController.isBluetoothEnabled();
+ boolean connecting = mBluetoothController.isBluetoothConnecting();
+ boolean turningOn =
+ mBluetoothController.getBluetoothState() == BluetoothAdapter.STATE_TURNING_ON;
+ if (connecting) {
+ subtitle = R.string.quick_settings_connecting;
+ } else if (turningOn) {
+ subtitle = R.string.quick_settings_bluetooth_secondary_label_transient;
+ } else if (enabled) {
+ subtitle = R.string.tap_a_device_to_connect;
+ }
+ mBluetoothDialogSubTitle.setText(mContext.getString(subtitle));
+ mBluetoothDialogSubTitle.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE);
+
+ // progress bar
+ boolean showProgress = connecting || turningOn;
+ mProgressBar.setVisibility(showProgress ? View.VISIBLE : View.GONE);
+ mDivider.setVisibility(showProgress ? View.GONE : View.VISIBLE);
+
+ // devices
+ Collection devices = mBluetoothController.getDevices();
+ if (!enabled || devices == null) {
+ mBluetoothRecyclerView.setVisibility(View.GONE);
+ mSeeAllLayout.setVisibility(View.GONE);
+ return;
+ }
+ boolean isOnCall = Utils.isAudioModeOngoingCall(mContext);
+ CachedBluetoothDevice activeDevice =
+ devices.stream()
+ .filter(device ->
+ (device.isActiveDevice(BluetoothProfile.HEADSET) && isOnCall)
+ || (device.isActiveDevice(BluetoothProfile.A2DP) && !isOnCall)
+ || device.isActiveDevice(BluetoothProfile.HEARING_AID)
+ || device.isActiveDevice(BluetoothProfile.LE_AUDIO)
+ )
+ .findFirst()
+ .orElse(null);
+ mBluetoothRecyclerView.setVisibility(View.VISIBLE);
+ mAdapter.setBluetoothDevices(new ArrayList(devices));
+ mAdapter.setActiveDevice(activeDevice);
+ mSeeAllLayout.setVisibility(devices.size() > MAX_DEVICES_COUNT ? View.VISIBLE : View.GONE);
+ }
+}
\ No newline at end of file
diff --git a/src/co/aospa/systemui/qs/tiles/dialog/BluetoothDialogFactory.kt b/src/co/aospa/systemui/qs/tiles/dialog/BluetoothDialogFactory.kt
new file mode 100644
index 0000000..4e6d3ec
--- /dev/null
+++ b/src/co/aospa/systemui/qs/tiles/dialog/BluetoothDialogFactory.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * (C) 2022 Paranoid Android
+ *
+ * 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 co.aospa.systemui.qs.tiles.dialog
+
+import android.content.Context
+import android.os.Handler
+import android.util.Log
+import android.view.View
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.BluetoothController
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private const val TAG = "BluetoothDialogFactory"
+private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+/**
+ * Factory to create [BluetoothDialog] objects.
+ */
+@SysUISingleton
+class BluetoothDialogFactory @Inject constructor(
+ @Main private val handler: Handler,
+ private val context: Context,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val activityStarter: ActivityStarter,
+ private val bluetoothController: BluetoothController
+) {
+ companion object {
+ var bluetoothDialog: BluetoothDialog? = null
+ }
+
+ /** Creates a [BluetoothDialog]. The dialog will be animated from [view] if it is not null. */
+ fun create(
+ aboveStatusBar: Boolean,
+ view: View?
+ ) {
+ if (bluetoothDialog != null) {
+ if (DEBUG) {
+ Log.d(TAG, "BluetoothDialog is showing, do not create it twice.")
+ }
+ return
+ } else {
+ bluetoothDialog = BluetoothDialog(context, this, aboveStatusBar, handler,
+ activityStarter, dialogLaunchAnimator, bluetoothController)
+ if (view != null) {
+ dialogLaunchAnimator.showFromView(bluetoothDialog!!, view,
+ animateBackgroundBoundsChange = true)
+ } else {
+ bluetoothDialog?.show()
+ }
+ }
+ }
+
+ fun destroyDialog() {
+ if (DEBUG) {
+ Log.d(TAG, "destroyDialog")
+ }
+ bluetoothDialog = null
+ }
+}
diff --git a/src/co/aospa/systemui/qs/tiles/dialog/BluetoothViewAdapter.java b/src/co/aospa/systemui/qs/tiles/dialog/BluetoothViewAdapter.java
new file mode 100644
index 0000000..9e7741b
--- /dev/null
+++ b/src/co/aospa/systemui/qs/tiles/dialog/BluetoothViewAdapter.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * (C) 2022 Paranoid Android
+ *
+ * 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 co.aospa.systemui.qs.tiles.dialog;
+
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.Html;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.settingslib.Utils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Adapter for showing bluetooth devices.
+ */
+public class BluetoothViewAdapter extends
+ RecyclerView.Adapter {
+
+ private static final String TAG = "BluetoothViewAdapter";
+
+ private static final String DEVICE_DETAIL_INTENT =
+ "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS";
+ private static final String KEY_DEVICE_ADDRESS = "device_address";
+ private static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
+
+ @Nullable
+ private List mDevices;
+ protected int mDevicesCount;
+ protected int mMaxDevicesCount = BluetoothDialog.MAX_DEVICES_COUNT;
+ private CachedBluetoothDevice mActiveDevice;
+
+ private BluetoothDialog mDialog;
+
+ protected View mHolderView;
+ protected Context mContext;
+
+ public BluetoothViewAdapter(BluetoothDialog dialog) {
+ mDialog = dialog;
+ }
+
+ @Override
+ public BluetoothViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
+ int viewType) {
+ mContext = viewGroup.getContext();
+ mHolderView = LayoutInflater.from(mContext).inflate(R.layout.bluetooth_list_item,
+ viewGroup, false);
+ return new BluetoothViewHolder(mHolderView, mDialog);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull BluetoothViewHolder viewHolder, int position) {
+ if (mDevices == null || position >= mDevicesCount) {
+ return;
+ }
+ CachedBluetoothDevice device = mDevices.get(position);
+ boolean isActive = mActiveDevice != null && position == 0;
+ if (isActive) {
+ device = mActiveDevice;
+ } else if (device == mActiveDevice) {
+ device = mDevices.get(0);
+ }
+ viewHolder.onBind(device, isActive);
+ }
+
+ /**
+ * Updates the connected bluetooth devices.
+ *
+ * @param devices the updated bluetooth devices.
+ */
+ public void setBluetoothDevices(List devices) {
+ if (mDevices != devices) {
+ mDevices = devices;
+ mDevicesCount = Math.min(devices.size(), mMaxDevicesCount);
+ notifyDataSetChanged();
+ }
+ }
+
+ public void setActiveDevice(@Nullable CachedBluetoothDevice device) {
+ if (mActiveDevice != device) {
+ mActiveDevice = device;
+ notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * Gets the total number of bluetooth devices.
+ *
+ * @return The total number of bluetooth devices.
+ */
+ @Override
+ public int getItemCount() {
+ return mDevicesCount;
+ }
+
+ /**
+ * ViewHolder for binding bluetooth view.
+ */
+ static class BluetoothViewHolder extends RecyclerView.ViewHolder {
+
+ final LinearLayout mContainerLayout;
+ final LinearLayout mBluetoothListLayout;
+ final LinearLayout mBluetoothDeviceLayout;
+ final ImageView mBluetoothIcon;
+ final TextView mBluetoothTitleText;
+ final TextView mBluetoothSummaryText;
+ final ImageView mBluetoothEndIcon;
+ final Context mContext;
+
+ final Drawable mBackgroundOn;
+ final Drawable mBackgroundOff;
+
+ final BluetoothDialog mDialog;
+
+ BluetoothViewHolder(View view, BluetoothDialog dialog) {
+ super(view);
+ mContext = view.getContext();
+ mDialog = dialog;
+ mContainerLayout = view.requireViewById(R.id.bluetooth_container);
+ mBluetoothListLayout = view.requireViewById(R.id.bluetooth_list);
+ mBluetoothDeviceLayout = view.requireViewById(R.id.bluetooth_device_layout);
+ mBluetoothIcon = view.requireViewById(R.id.bluetooth_icon);
+ mBluetoothTitleText = view.requireViewById(R.id.bluetooth_title);
+ mBluetoothSummaryText = view.requireViewById(R.id.bluetooth_summary);
+ mBluetoothEndIcon = view.requireViewById(R.id.bluetooth_end_icon);
+ mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
+
+ TypedArray typedArray = mContext.obtainStyledAttributes(
+ new int[]{android.R.attr.selectableItemBackground});
+ try {
+ mBackgroundOff = typedArray.getDrawable(0 /* index */);
+ } finally {
+ typedArray.recycle();
+ }
+ }
+
+ void onBind(@NonNull CachedBluetoothDevice device, boolean isActive) {
+ if (device == null || !device.hasHumanReadableName()) {
+ mBluetoothListLayout.setVisibility(View.GONE);
+ return;
+ }
+ mBluetoothListLayout.setVisibility(View.VISIBLE);
+ mBluetoothListLayout.setBackground(isActive ? mBackgroundOn : mBackgroundOff);
+ mBluetoothListLayout.setOnClickListener(v -> {
+ if (isActive) {
+ device.disconnect();
+ } else if (device.isConnected()) {
+ device.setActive();
+ } else {
+ device.connect();
+ }
+ });
+
+ // device icon
+ mBluetoothIcon.setImageDrawable(device.getDrawableWithDescription().first);
+
+ // title
+ mBluetoothTitleText.setText(device.getName());
+ mBluetoothTitleText.setTextAppearance(isActive
+ ? R.style.TextAppearance_InternetDialog_Active
+ : R.style.TextAppearance_InternetDialog);
+
+ // summary
+ String summary = device.getConnectionSummary();
+ boolean showSummary = !TextUtils.isEmpty(summary);
+ if (showSummary) {
+ mBluetoothSummaryText.setText(summary);
+ mBluetoothSummaryText.setTextAppearance(isActive
+ ? R.style.TextAppearance_InternetDialog_Secondary_Active
+ : R.style.TextAppearance_InternetDialog_Secondary);
+ }
+ mBluetoothSummaryText.setVisibility(showSummary ? View.VISIBLE : View.GONE);
+
+ int iconColor = isActive ? mContext.getColor(R.color.connected_network_primary_color)
+ : Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal);
+ mBluetoothEndIcon.setColorFilter(iconColor);
+
+ final Bundle args = new Bundle();
+ args.putString(KEY_DEVICE_ADDRESS, device.getAddress());
+ mBluetoothEndIcon.setOnClickListener(v -> {
+ mDialog.startActivity(new Intent(DEVICE_DETAIL_INTENT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args), v);
+ });
+ }
+ }
+}