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