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