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.widget.ImageView; 34 35 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 36 import com.android.settings.R; 37 import com.android.settings.core.instrumentation.MetricsFeatureProvider; 38 import com.android.settings.overlay.FeatureFactory; 39 import com.android.settings.widget.GearPreference; 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 GearPreference implements 53 CachedBluetoothDevice.Callback { 54 private static final String TAG = "BluetoothDevicePref"; 55 56 private static int sDimAlpha = Integer.MIN_VALUE; 57 58 private final CachedBluetoothDevice mCachedDevice; 59 private final UserManager mUserManager; 60 61 private AlertDialog mDisconnectDialog; 62 63 private String contentDescription = null; 64 65 /* Talk-back descriptions for various BT icons */ 66 Resources r = getContext().getResources(); 67 public final String COMPUTER = r.getString(R.string.bluetooth_talkback_computer); 68 public final String INPUT_PERIPHERAL = r.getString( 69 R.string.bluetooth_talkback_input_peripheral); 70 public final String HEADSET = r.getString(R.string.bluetooth_talkback_headset); 71 public final String PHONE = r.getString(R.string.bluetooth_talkback_phone); 72 public final String IMAGING = r.getString(R.string.bluetooth_talkback_imaging); 73 public final String HEADPHONE = r.getString(R.string.bluetooth_talkback_headphone); 74 public final String BLUETOOTH = r.getString(R.string.bluetooth_talkback_bluetooth); 75 BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice)76 public BluetoothDevicePreference(Context context, CachedBluetoothDevice cachedDevice) { 77 super(context, null); 78 mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); 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 mCachedDevice.registerCallback(this); 88 89 onDeviceAttributesChanged(); 90 } 91 rebind()92 void rebind() { 93 notifyChanged(); 94 } 95 96 @Override shouldHideSecondTarget()97 protected boolean shouldHideSecondTarget() { 98 return mCachedDevice == null 99 || mCachedDevice.getBondState() != BluetoothDevice.BOND_BONDED 100 || mUserManager.hasUserRestriction(DISALLOW_CONFIG_BLUETOOTH); 101 } 102 103 @Override getSecondTargetResId()104 protected int getSecondTargetResId() { 105 return R.layout.preference_widget_gear; 106 } 107 getCachedDevice()108 CachedBluetoothDevice getCachedDevice() { 109 return mCachedDevice; 110 } 111 112 @Override onPrepareForRemoval()113 protected void onPrepareForRemoval() { 114 super.onPrepareForRemoval(); 115 mCachedDevice.unregisterCallback(this); 116 if (mDisconnectDialog != null) { 117 mDisconnectDialog.dismiss(); 118 mDisconnectDialog = null; 119 } 120 } 121 getBluetoothDevice()122 public CachedBluetoothDevice getBluetoothDevice() { 123 return mCachedDevice; 124 } 125 onDeviceAttributesChanged()126 public void onDeviceAttributesChanged() { 127 /* 128 * The preference framework takes care of making sure the value has 129 * changed before proceeding. It will also call notifyChanged() if 130 * any preference info has changed from the previous value. 131 */ 132 setTitle(mCachedDevice.getName()); 133 134 int summaryResId = mCachedDevice.getConnectionSummary(); 135 if (summaryResId != 0) { 136 setSummary(summaryResId); 137 } else { 138 setSummary(null); // empty summary for unpaired devices 139 } 140 141 142 Pair<Integer, String> pair = getBtClassDrawableWithDescription(); 143 if (pair.first != 0) { 144 setIcon(pair.first); 145 contentDescription = pair.second; 146 } 147 148 // Used to gray out the item 149 setEnabled(!mCachedDevice.isBusy()); 150 151 // This could affect ordering, so notify that 152 notifyHierarchyChanged(); 153 } 154 155 @Override onBindViewHolder(PreferenceViewHolder view)156 public void onBindViewHolder(PreferenceViewHolder view) { 157 // Disable this view if the bluetooth enable/disable preference view is off 158 if (null != findPreferenceInHierarchy("bt_checkbox")) { 159 setDependency("bt_checkbox"); 160 } 161 162 if (mCachedDevice.getBondState() == BluetoothDevice.BOND_BONDED) { 163 ImageView deviceDetails = (ImageView) view.findViewById(R.id.settings_button); 164 165 if (deviceDetails != null) { 166 deviceDetails.setOnClickListener(this); 167 } 168 } 169 final ImageView imageView = (ImageView) view.findViewById(android.R.id.icon); 170 if (imageView != null) { 171 imageView.setContentDescription(contentDescription); 172 } 173 super.onBindViewHolder(view); 174 } 175 176 @Override equals(Object o)177 public boolean equals(Object o) { 178 if ((o == null) || !(o instanceof BluetoothDevicePreference)) { 179 return false; 180 } 181 return mCachedDevice.equals( 182 ((BluetoothDevicePreference) o).mCachedDevice); 183 } 184 185 @Override hashCode()186 public int hashCode() { 187 return mCachedDevice.hashCode(); 188 } 189 190 @Override compareTo(Preference another)191 public int compareTo(Preference another) { 192 if (!(another instanceof BluetoothDevicePreference)) { 193 // Rely on default sort 194 return super.compareTo(another); 195 } 196 197 return mCachedDevice 198 .compareTo(((BluetoothDevicePreference) another).mCachedDevice); 199 } 200 onClicked()201 void onClicked() { 202 int bondState = mCachedDevice.getBondState(); 203 204 final MetricsFeatureProvider metricsFeatureProvider = 205 FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider(); 206 207 if (mCachedDevice.isConnected()) { 208 metricsFeatureProvider.action(getContext(), 209 MetricsEvent.ACTION_SETTINGS_BLUETOOTH_DISCONNECT); 210 askDisconnect(); 211 } else if (bondState == BluetoothDevice.BOND_BONDED) { 212 metricsFeatureProvider.action(getContext(), 213 MetricsEvent.ACTION_SETTINGS_BLUETOOTH_CONNECT); 214 mCachedDevice.connect(true); 215 } else if (bondState == BluetoothDevice.BOND_NONE) { 216 metricsFeatureProvider.action(getContext(), 217 MetricsEvent.ACTION_SETTINGS_BLUETOOTH_PAIR); 218 pair(); 219 } 220 } 221 222 // Show disconnect confirmation dialog for a device. askDisconnect()223 private void askDisconnect() { 224 Context context = getContext(); 225 String name = mCachedDevice.getName(); 226 if (TextUtils.isEmpty(name)) { 227 name = context.getString(R.string.bluetooth_device); 228 } 229 String message = context.getString(R.string.bluetooth_disconnect_all_profiles, name); 230 String title = context.getString(R.string.bluetooth_disconnect_title); 231 232 DialogInterface.OnClickListener disconnectListener = new DialogInterface.OnClickListener() { 233 public void onClick(DialogInterface dialog, int which) { 234 mCachedDevice.disconnect(); 235 } 236 }; 237 238 mDisconnectDialog = Utils.showDisconnectDialog(context, 239 mDisconnectDialog, disconnectListener, title, Html.fromHtml(message)); 240 } 241 pair()242 private void pair() { 243 if (!mCachedDevice.startPairing()) { 244 Utils.showError(getContext(), mCachedDevice.getName(), 245 R.string.bluetooth_pairing_error_message); 246 } 247 } 248 getBtClassDrawableWithDescription()249 private Pair<Integer, String> getBtClassDrawableWithDescription() { 250 BluetoothClass btClass = mCachedDevice.getBtClass(); 251 if (btClass != null) { 252 switch (btClass.getMajorDeviceClass()) { 253 case BluetoothClass.Device.Major.COMPUTER: 254 return new Pair<Integer, String>(R.drawable.ic_bt_laptop, COMPUTER); 255 256 case BluetoothClass.Device.Major.PHONE: 257 return new Pair<Integer, String>(R.drawable.ic_bt_cellphone, PHONE); 258 259 case BluetoothClass.Device.Major.PERIPHERAL: 260 return new Pair<Integer, String>(HidProfile.getHidClassDrawable(btClass), 261 INPUT_PERIPHERAL); 262 263 case BluetoothClass.Device.Major.IMAGING: 264 return new Pair<Integer, String>(R.drawable.ic_bt_imaging, IMAGING); 265 266 default: 267 // unrecognized device class; continue 268 } 269 } else { 270 Log.w(TAG, "mBtClass is null"); 271 } 272 273 List<LocalBluetoothProfile> profiles = mCachedDevice.getProfiles(); 274 for (LocalBluetoothProfile profile : profiles) { 275 int resId = profile.getDrawableResource(btClass); 276 if (resId != 0) { 277 return new Pair<Integer, String>(resId, null); 278 } 279 } 280 if (btClass != null) { 281 if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { 282 return new Pair<Integer, String>(R.drawable.ic_bt_headset_hfp, HEADSET); 283 } 284 if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { 285 return new Pair<Integer, String>(R.drawable.ic_bt_headphones_a2dp, HEADPHONE); 286 } 287 } 288 return new Pair<Integer, String>(R.drawable.ic_settings_bluetooth, BLUETOOTH); 289 } 290 } 291