1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.bluetooth;
18 
19 import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
20 
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothDevice;
23 import android.os.Bundle;
24 import android.util.Log;
25 import android.view.View;
26 import android.widget.Toast;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 import androidx.annotation.VisibleForTesting;
31 
32 import com.android.settings.R;
33 import com.android.settings.accessibility.AccessibilityStatsLogUtils;
34 import com.android.settings.overlay.FeatureFactory;
35 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
36 import com.android.settingslib.bluetooth.HearingAidStatsLogUtils;
37 
38 /**
39  * Abstract class for providing basic interaction for a list of Bluetooth devices in bluetooth
40  * device pairing detail page.
41  */
42 public abstract class BluetoothDevicePairingDetailBase extends DeviceListPreferenceFragment {
43 
44     protected boolean mInitialScanStarted;
45     @VisibleForTesting
46     protected BluetoothProgressCategory mAvailableDevicesCategory;
47 
BluetoothDevicePairingDetailBase()48     public BluetoothDevicePairingDetailBase() {
49         super(DISALLOW_CONFIG_BLUETOOTH);
50     }
51 
52     @Override
initPreferencesFromPreferenceScreen()53     public void initPreferencesFromPreferenceScreen() {
54         mAvailableDevicesCategory = findPreference(getDeviceListKey());
55     }
56 
57     @Override
onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)58     public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
59         mInitialScanStarted = false;
60         super.onViewCreated(view, savedInstanceState);
61     }
62 
63     @Override
onStart()64     public void onStart() {
65         super.onStart();
66         if (mLocalManager == null) {
67             Log.e(getLogTag(), "Bluetooth is not supported on this device");
68             return;
69         }
70         updateBluetooth();
71     }
72 
73     @Override
onStop()74     public void onStop() {
75         super.onStop();
76         if (mLocalManager == null) {
77             Log.e(getLogTag(), "Bluetooth is not supported on this device");
78             return;
79         }
80         disableScanning();
81     }
82 
83     @Override
onBluetoothStateChanged(int bluetoothState)84     public void onBluetoothStateChanged(int bluetoothState) {
85         super.onBluetoothStateChanged(bluetoothState);
86         updateContent(bluetoothState);
87         if (bluetoothState == BluetoothAdapter.STATE_ON) {
88             showBluetoothTurnedOnToast();
89         }
90     }
91 
92     @Override
onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState)93     public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
94         if (bondState == BluetoothDevice.BOND_BONDED) {
95             // If one device is connected(bonded), then close this fragment.
96             finish();
97             return;
98         } else if (bondState == BluetoothDevice.BOND_BONDING) {
99             // Set the bond entry where binding process starts for logging hearing aid device info
100             final int pageId = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
101                     .getAttribution(getActivity());
102             final int bondEntry = AccessibilityStatsLogUtils.convertToHearingAidInfoBondEntry(
103                     pageId);
104             HearingAidStatsLogUtils.setBondEntryForDevice(bondEntry, cachedDevice);
105         }
106         if (mSelectedDevice != null && cachedDevice != null) {
107             BluetoothDevice device = cachedDevice.getDevice();
108             if (device != null && mSelectedDevice.equals(device)
109                     && bondState == BluetoothDevice.BOND_NONE) {
110                 // If currently selected device failed to bond, restart scanning
111                 enableScanning();
112             }
113         }
114     }
115 
116     @Override
onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state, int bluetoothProfile)117     public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state,
118             int bluetoothProfile) {
119         // This callback is used to handle the case that bonded device is connected in pairing list.
120         // 1. If user selected multiple bonded devices in pairing list, after connected
121         // finish this page.
122         // 2. If the bonded devices auto connected in paring list, after connected it will be
123         // removed from paring list.
124         if (cachedDevice != null && cachedDevice.isConnected()) {
125             final BluetoothDevice device = cachedDevice.getDevice();
126             if (device != null && mSelectedList.contains(device)) {
127                 finish();
128             } else {
129                 onDeviceDeleted(cachedDevice);
130             }
131         }
132     }
133 
134     @Override
enableScanning()135     public void enableScanning() {
136         // Clear all device states before first scan
137         if (!mInitialScanStarted) {
138             if (mAvailableDevicesCategory != null) {
139                 removeAllDevices();
140             }
141             mLocalManager.getCachedDeviceManager().clearNonBondedDevices();
142             mInitialScanStarted = true;
143         }
144         super.enableScanning();
145     }
146 
147     @Override
onDevicePreferenceClick(BluetoothDevicePreference btPreference)148     public void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
149         disableScanning();
150         super.onDevicePreferenceClick(btPreference);
151     }
152 
153     @VisibleForTesting
updateBluetooth()154     void updateBluetooth() {
155         if (mBluetoothAdapter.isEnabled()) {
156             updateContent(mBluetoothAdapter.getState());
157         } else {
158             // Turn on bluetooth if it is disabled
159             mBluetoothAdapter.enable();
160         }
161     }
162 
163     /**
164      * Enables the scanning when {@code bluetoothState} is on, or finish the page when
165      * {@code bluetoothState} is off.
166      *
167      * @param bluetoothState the current Bluetooth state, the possible values that will handle here:
168      * {@link android.bluetooth.BluetoothAdapter#STATE_OFF},
169      * {@link android.bluetooth.BluetoothAdapter#STATE_ON},
170      */
171     @VisibleForTesting
updateContent(int bluetoothState)172     public void updateContent(int bluetoothState) {
173         switch (bluetoothState) {
174             case BluetoothAdapter.STATE_ON:
175                 mBluetoothAdapter.enable();
176                 enableScanning();
177                 break;
178 
179             case BluetoothAdapter.STATE_OFF:
180                 finish();
181                 break;
182         }
183     }
184 
185     @VisibleForTesting
showBluetoothTurnedOnToast()186     void showBluetoothTurnedOnToast() {
187         Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast,
188                 Toast.LENGTH_SHORT).show();
189     }
190 }
191