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