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