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