1 /*
2  * Copyright 2018 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.car.settings.bluetooth;
18 
19 import android.bluetooth.BluetoothProfile;
20 import android.content.Context;
21 import android.graphics.drawable.Drawable;
22 import android.os.SystemProperties;
23 import android.util.AttributeSet;
24 import android.util.Pair;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 import androidx.preference.Preference;
29 
30 import com.android.car.settings.R;
31 import com.android.car.settings.common.MultiActionPreference;
32 import com.android.car.settings.common.ToggleButtonActionItem;
33 import com.android.settingslib.bluetooth.BluetoothUtils;
34 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
35 
36 /**
37  * Preference which represents a specific {@link CachedBluetoothDevice}. The title, icon, and
38  * summary are kept in sync with the device when the preference is shown. When the device is busy,
39  * the preference is disabled. The equality and sort order of this preference is determined by the
40  * underlying cached device {@link CachedBluetoothDevice#equals(Object)} and {@link
41  * CachedBluetoothDevice#compareTo(CachedBluetoothDevice)}. If two devices are considered equal, the
42  * default preference sort ordering is used (see {@link #compareTo(Preference)}.
43  */
44 public class BluetoothDevicePreference extends MultiActionPreference {
45     private static final String BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY =
46             "persist.bluetooth.showdeviceswithoutnames";
47 
48     private final CachedBluetoothDevice mCachedDevice;
49     private final boolean mShowDevicesWithoutNames;
50     private final CachedBluetoothDevice.Callback mDeviceCallback = this::refreshUi;
51 
52     private UpdateToggleButtonListener mUpdateToggleButtonListener;
53 
BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice)54     public BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice) {
55         super(context);
56         mCachedDevice = cachedDevice;
57         mShowDevicesWithoutNames = SystemProperties.getBoolean(
58                 BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false);
59     }
60 
61     @Override
init(@ullable AttributeSet attrs)62     protected void init(@Nullable AttributeSet attrs) {
63         mActionItemArray[0] = new ToggleButtonActionItem(this);
64         mActionItemArray[1] = new ToggleButtonActionItem(this);
65         mActionItemArray[2] = new ToggleButtonActionItem(this);
66         super.init(attrs);
67 
68         // Hide actions by default.
69         mActionItemArray[0].setVisible(false);
70         mActionItemArray[1].setVisible(false);
71         mActionItemArray[2].setVisible(false);
72     }
73 
74     /**
75      * Returns the {@link CachedBluetoothDevice} represented by this preference.
76      */
getCachedDevice()77     public CachedBluetoothDevice getCachedDevice() {
78         return mCachedDevice;
79     }
80 
81     /**
82      * Sets the {@link UpdateToggleButtonListener} that will be called when the toggle buttons
83      * may need to change state.
84      */
setToggleButtonUpdateListener(UpdateToggleButtonListener listener)85     public void setToggleButtonUpdateListener(UpdateToggleButtonListener listener) {
86         mUpdateToggleButtonListener = listener;
87     }
88 
89     @Override
onAttached()90     public void onAttached() {
91         super.onAttached();
92         mCachedDevice.registerCallback(mDeviceCallback);
93         refreshUi();
94     }
95 
96     @Override
onDetached()97     public void onDetached() {
98         super.onDetached();
99         mCachedDevice.unregisterCallback(mDeviceCallback);
100     }
101 
refreshUi()102     private void refreshUi() {
103         setTitle(mCachedDevice.getName());
104 
105         // If connected, we only want the "Connected" text without details (ex. "no media")
106         // TODO: Move branching logic into getCarConnectionSummary()
107         if (mCachedDevice.isConnected()) {
108             setSummary(getContext().getString(BluetoothUtils
109                     .getConnectionStateSummary(BluetoothProfile.STATE_CONNECTED),
110                     /* appended text= */ ""));
111         } else {
112             setSummary(mCachedDevice.getCarConnectionSummary());
113         }
114 
115         Pair<Drawable, String> pair = com.android.settingslib.bluetooth.BluetoothUtils
116                 .getBtClassDrawableWithDescription(getContext(), mCachedDevice);
117         if (pair.first != null) {
118             setIcon(pair.first);
119             getIcon().setTintList(getContext().getColorStateList(R.color.icon_color_default));
120         }
121 
122         setEnabled(!mCachedDevice.isBusy());
123         setVisible(mShowDevicesWithoutNames || mCachedDevice.hasHumanReadableName());
124 
125         if (mUpdateToggleButtonListener != null) {
126             mUpdateToggleButtonListener.updateToggleButtonState(this);
127         }
128         // Notify since the ordering may have changed.
129         notifyHierarchyChanged();
130     }
131 
132     @Override
equals(Object o)133     public boolean equals(Object o) {
134         if (!(o instanceof BluetoothDevicePreference)) {
135             return false;
136         }
137         return mCachedDevice.equals(((BluetoothDevicePreference) o).mCachedDevice);
138     }
139 
140     @Override
hashCode()141     public int hashCode() {
142         return mCachedDevice.hashCode();
143     }
144 
145     @Override
compareTo(@onNull Preference another)146     public int compareTo(@NonNull Preference another) {
147         if (!(another instanceof BluetoothDevicePreference)) {
148             // Rely on default sort.
149             return super.compareTo(another);
150         }
151 
152         return mCachedDevice
153                 .compareTo(((BluetoothDevicePreference) another).mCachedDevice);
154     }
155 
156     @Override
getActionItem(ActionItem actionItem)157     public ToggleButtonActionItem getActionItem(ActionItem actionItem) {
158         switch(actionItem) {
159             case ACTION_ITEM1:
160                 return (ToggleButtonActionItem) mActionItemArray[0];
161             case ACTION_ITEM2:
162                 return (ToggleButtonActionItem) mActionItemArray[1];
163             case ACTION_ITEM3:
164                 return (ToggleButtonActionItem) mActionItemArray[2];
165             default:
166                 throw new IllegalArgumentException("Invalid button requested");
167         }
168     }
169 
170     /**
171      * Callback for when toggle buttons may need to be updated
172      */
173     public interface UpdateToggleButtonListener {
174         /**
175          * Preference state has changed and toggle button changes should be handled.
176          *
177          * @param preference the preference that has been changed
178          */
updateToggleButtonState(BluetoothDevicePreference preference)179         void updateToggleButtonState(BluetoothDevicePreference preference);
180     }
181 }
182