1 /*
2  * Copyright (C) 2008 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.app.AlertDialog;
22 import android.bluetooth.BluetoothClass;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.BluetoothProfile;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.os.UserManager;
28 import android.preference.Preference;
29 import android.text.Html;
30 import android.text.TextUtils;
31 import android.util.Log;
32 import android.util.TypedValue;
33 import android.view.View;
34 import android.view.View.OnClickListener;
35 import android.widget.ImageView;
36 
37 import com.android.settings.R;
38 import com.android.settings.search.Index;
39 import com.android.settings.search.SearchIndexableRaw;
40 
41 import java.util.List;
42 
43 /**
44  * BluetoothDevicePreference is the preference type used to display each remote
45  * Bluetooth device in the Bluetooth Settings screen.
46  */
47 public final class BluetoothDevicePreference extends Preference implements
48         CachedBluetoothDevice.Callback, OnClickListener {
49     private static final String TAG = "BluetoothDevicePreference";
50 
51     private static int sDimAlpha = Integer.MIN_VALUE;
52 
53     private final CachedBluetoothDevice mCachedDevice;
54 
55     private OnClickListener mOnSettingsClickListener;
56 
57     private AlertDialog mDisconnectDialog;
58 
BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice)59     public BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice) {
60         super(context);
61 
62         if (sDimAlpha == Integer.MIN_VALUE) {
63             TypedValue outValue = new TypedValue();
64             context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
65             sDimAlpha = (int) (outValue.getFloat() * 255);
66         }
67 
68         mCachedDevice = cachedDevice;
69 
70         setLayoutResource(R.layout.preference_bt_icon);
71 
72         if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
73             UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
74             if (! um.hasUserRestriction(DISALLOW_CONFIG_BLUETOOTH)) {
75                 setWidgetLayoutResource(R.layout.preference_bluetooth);
76             }
77         }
78 
79         mCachedDevice.registerCallback(this);
80 
81         onDeviceAttributesChanged();
82     }
83 
getCachedDevice()84     CachedBluetoothDevice getCachedDevice() {
85         return mCachedDevice;
86     }
87 
setOnSettingsClickListener(OnClickListener listener)88     public void setOnSettingsClickListener(OnClickListener listener) {
89         mOnSettingsClickListener = listener;
90     }
91 
92     @Override
onPrepareForRemoval()93     protected void onPrepareForRemoval() {
94         super.onPrepareForRemoval();
95         mCachedDevice.unregisterCallback(this);
96         if (mDisconnectDialog != null) {
97             mDisconnectDialog.dismiss();
98             mDisconnectDialog = null;
99         }
100     }
101 
onDeviceAttributesChanged()102     public void onDeviceAttributesChanged() {
103         /*
104          * The preference framework takes care of making sure the value has
105          * changed before proceeding. It will also call notifyChanged() if
106          * any preference info has changed from the previous value.
107          */
108         setTitle(mCachedDevice.getName());
109 
110         int summaryResId = getConnectionSummary();
111         if (summaryResId != 0) {
112             setSummary(summaryResId);
113         } else {
114             setSummary(null);   // empty summary for unpaired devices
115         }
116 
117         int iconResId = getBtClassDrawable();
118         if (iconResId != 0) {
119             setIcon(iconResId);
120         }
121 
122         // Used to gray out the item
123         setEnabled(!mCachedDevice.isBusy());
124 
125         // This could affect ordering, so notify that
126         notifyHierarchyChanged();
127     }
128 
129     @Override
onBindView(View view)130     protected void onBindView(View view) {
131         // Disable this view if the bluetooth enable/disable preference view is off
132         if (null != findPreferenceInHierarchy("bt_checkbox")) {
133             setDependency("bt_checkbox");
134         }
135 
136         if (mCachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) {
137             ImageView deviceDetails = (ImageView) view.findViewById(R.id.deviceDetails);
138 
139             if (deviceDetails != null) {
140                 deviceDetails.setOnClickListener(this);
141                 deviceDetails.setTag(mCachedDevice);
142             }
143         }
144 
145         super.onBindView(view);
146     }
147 
onClick(View v)148     public void onClick(View v) {
149         // Should never be null by construction
150         if (mOnSettingsClickListener != null) {
151             mOnSettingsClickListener.onClick(v);
152         }
153     }
154 
155     @Override
equals(Object o)156     public boolean equals(Object o) {
157         if ((o == null) || !(o instanceof BluetoothDevicePreference)) {
158             return false;
159         }
160         return mCachedDevice.equals(
161                 ((BluetoothDevicePreference) o).mCachedDevice);
162     }
163 
164     @Override
hashCode()165     public int hashCode() {
166         return mCachedDevice.hashCode();
167     }
168 
169     @Override
compareTo(Preference another)170     public int compareTo(Preference another) {
171         if (!(another instanceof BluetoothDevicePreference)) {
172             // Rely on default sort
173             return super.compareTo(another);
174         }
175 
176         return mCachedDevice
177                 .compareTo(((BluetoothDevicePreference) another).mCachedDevice);
178     }
179 
onClicked()180     void onClicked() {
181         int bondState = mCachedDevice.getBondState();
182 
183         if (mCachedDevice.isConnected()) {
184             askDisconnect();
185         } else if (bondState == BluetoothDevice.BOND_BONDED) {
186             mCachedDevice.connect(true);
187         } else if (bondState == BluetoothDevice.BOND_NONE) {
188             pair();
189         }
190     }
191 
192     // Show disconnect confirmation dialog for a device.
askDisconnect()193     private void askDisconnect() {
194         Context context = getContext();
195         String name = mCachedDevice.getName();
196         if (TextUtils.isEmpty(name)) {
197             name = context.getString(R.string.bluetooth_device);
198         }
199         String message = context.getString(R.string.bluetooth_disconnect_all_profiles, name);
200         String title = context.getString(R.string.bluetooth_disconnect_title);
201 
202         DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() {
203             public void onClick(DialogInterface dialog, int which) {
204                 mCachedDevice.disconnect();
205             }
206         };
207 
208         mDisconnectDialog = Utils.showDisconnectDialog(context,
209                 mDisconnectDialog, disconnectListener, title, Html.fromHtml(message));
210     }
211 
pair()212     private void pair() {
213         if (!mCachedDevice.startPairing()) {
214             Utils.showError(getContext(), mCachedDevice.getName(),
215                     R.string.bluetooth_pairing_error_message);
216         } else {
217             final Context context = getContext();
218 
219             SearchIndexableRaw data = new SearchIndexableRaw(context);
220             data.className = BluetoothSettings.class.getName();
221             data.title = mCachedDevice.getName();
222             data.screenTitle = context.getResources().getString(R.string.bluetooth_settings);
223             data.iconResId = R.drawable.ic_settings_bluetooth2;
224             data.enabled = true;
225 
226             Index.getInstance(context).updateFromSearchIndexableData(data);
227         }
228     }
229 
getConnectionSummary()230     private int getConnectionSummary() {
231         final CachedBluetoothDevice cachedDevice = mCachedDevice;
232 
233         boolean profileConnected = false;       // at least one profile is connected
234         boolean a2dpNotConnected = false;       // A2DP is preferred but not connected
235         boolean headsetNotConnected = false;    // Headset is preferred but not connected
236 
237         for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
238             int connectionStatus = cachedDevice.getProfileConnectionState(profile);
239 
240             switch (connectionStatus) {
241                 case BluetoothProfile.STATE_CONNECTING:
242                 case BluetoothProfile.STATE_DISCONNECTING:
243                     return Utils.getConnectionStateSummary(connectionStatus);
244 
245                 case BluetoothProfile.STATE_CONNECTED:
246                     profileConnected = true;
247                     break;
248 
249                 case BluetoothProfile.STATE_DISCONNECTED:
250                     if (profile.isProfileReady()) {
251                         if (profile instanceof A2dpProfile) {
252                             a2dpNotConnected = true;
253                         } else if (profile instanceof HeadsetProfile) {
254                             headsetNotConnected = true;
255                         }
256                     }
257                     break;
258             }
259         }
260 
261         if (profileConnected) {
262             if (a2dpNotConnected && headsetNotConnected) {
263                 return R.string.bluetooth_connected_no_headset_no_a2dp;
264             } else if (a2dpNotConnected) {
265                 return R.string.bluetooth_connected_no_a2dp;
266             } else if (headsetNotConnected) {
267                 return R.string.bluetooth_connected_no_headset;
268             } else {
269                 return R.string.bluetooth_connected;
270             }
271         }
272 
273         switch (cachedDevice.getBondState()) {
274             case BluetoothDevice.BOND_BONDING:
275                 return R.string.bluetooth_pairing;
276 
277             case BluetoothDevice.BOND_BONDED:
278             case BluetoothDevice.BOND_NONE:
279             default:
280                 return 0;
281         }
282     }
283 
getBtClassDrawable()284     private int getBtClassDrawable() {
285         BluetoothClass btClass = mCachedDevice.getBtClass();
286         if (btClass != null) {
287             switch (btClass.getMajorDeviceClass()) {
288                 case BluetoothClass.Device.Major.COMPUTER:
289                     return R.drawable.ic_bt_laptop;
290 
291                 case BluetoothClass.Device.Major.PHONE:
292                     return R.drawable.ic_bt_cellphone;
293 
294                 case BluetoothClass.Device.Major.PERIPHERAL:
295                     return HidProfile.getHidClassDrawable(btClass);
296 
297                 case BluetoothClass.Device.Major.IMAGING:
298                     return R.drawable.ic_bt_imaging;
299 
300                 default:
301                     // unrecognized device class; continue
302             }
303         } else {
304             Log.w(TAG, "mBtClass is null");
305         }
306 
307         List<LocalBluetoothProfile> profiles = mCachedDevice.getProfiles();
308         for (LocalBluetoothProfile profile : profiles) {
309             int resId = profile.getDrawableResource(btClass);
310             if (resId != 0) {
311                 return resId;
312             }
313         }
314         if (btClass != null) {
315             if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) {
316                 return R.drawable.ic_bt_headphones_a2dp;
317 
318             }
319             if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
320                 return R.drawable.ic_bt_headset_hfp;
321             }
322         }
323         return R.drawable.ic_settings_bluetooth2;
324     }
325 }
326