1 /* 2 * Copyright (C) 2016 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 package com.android.settings.bluetooth; 17 18 import android.bluetooth.BluetoothClass; 19 import android.bluetooth.BluetoothDevice; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.text.Editable; 23 import android.util.Log; 24 import android.widget.CompoundButton; 25 import android.widget.CompoundButton.OnCheckedChangeListener; 26 import com.android.settings.R; 27 import com.android.settings.bluetooth.BluetoothPairingDialogFragment.BluetoothPairingDialogListener; 28 import com.android.settingslib.bluetooth.LocalBluetoothManager; 29 import com.android.settingslib.bluetooth.LocalBluetoothProfile; 30 import java.util.Locale; 31 32 /** 33 * A controller used by {@link BluetoothPairingDialog} to manage connection state while we try to 34 * pair with a bluetooth device. It includes methods that allow the 35 * {@link BluetoothPairingDialogFragment} to interrogate the current state as well. 36 */ 37 public class BluetoothPairingController implements OnCheckedChangeListener, 38 BluetoothPairingDialogListener { 39 40 private static final String TAG = "BTPairingController"; 41 42 // Different types of dialogs we can map to 43 public static final int INVALID_DIALOG_TYPE = -1; 44 public static final int USER_ENTRY_DIALOG = 0; 45 public static final int CONFIRMATION_DIALOG = 1; 46 public static final int DISPLAY_PASSKEY_DIALOG = 2; 47 48 private static final int BLUETOOTH_PIN_MAX_LENGTH = 16; 49 private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6; 50 51 // Bluetooth dependencies for the connection we are trying to establish 52 private LocalBluetoothManager mBluetoothManager; 53 private BluetoothDevice mDevice; 54 private int mType; 55 private String mUserInput; 56 private String mPasskeyFormatted; 57 private int mPasskey; 58 private String mDeviceName; 59 private LocalBluetoothProfile mPbapClientProfile; 60 private boolean mPbapAllowed; 61 62 /** 63 * Creates an instance of a BluetoothPairingController. 64 * 65 * @param intent - must contain {@link BluetoothDevice#EXTRA_PAIRING_VARIANT}, {@link 66 * BluetoothDevice#EXTRA_PAIRING_KEY}, and {@link BluetoothDevice#EXTRA_DEVICE}. Missing extra 67 * will lead to undefined behavior. 68 */ 69 public BluetoothPairingController(Intent intent, Context context) { 70 mBluetoothManager = Utils.getLocalBtManager(context); 71 mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 72 73 String message = ""; 74 if (mBluetoothManager == null) { 75 throw new IllegalStateException("Could not obtain LocalBluetoothManager"); 76 } else if (mDevice == null) { 77 throw new IllegalStateException("Could not find BluetoothDevice"); 78 } 79 80 mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR); 81 mPasskey = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR); 82 mDeviceName = mBluetoothManager.getCachedDeviceManager().getName(mDevice); 83 mPbapClientProfile = mBluetoothManager.getProfileManager().getPbapClientProfile(); 84 mPasskeyFormatted = formatKey(mPasskey); 85 86 } 87 88 @Override 89 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 90 if (isChecked) { 91 mPbapAllowed = true; 92 } else { 93 mPbapAllowed = false; 94 } 95 } 96 97 @Override 98 public void onDialogPositiveClick(BluetoothPairingDialogFragment dialog) { 99 if (getDialogType() == USER_ENTRY_DIALOG) { 100 if (mPbapAllowed) { 101 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); 102 } else { 103 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED); 104 } 105 onPair(mUserInput); 106 } else { 107 onPair(null); 108 } 109 } 110 111 @Override 112 public void onDialogNegativeClick(BluetoothPairingDialogFragment dialog) { 113 mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED); 114 onCancel(); 115 } 116 117 /** 118 * A method for querying which bluetooth pairing dialog fragment variant this device requires. 119 * 120 * @return - The dialog view variant needed for this device. 121 */ 122 public int getDialogType() { 123 switch (mType) { 124 case BluetoothDevice.PAIRING_VARIANT_PIN: 125 case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS: 126 case BluetoothDevice.PAIRING_VARIANT_PASSKEY: 127 return USER_ENTRY_DIALOG; 128 129 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: 130 case BluetoothDevice.PAIRING_VARIANT_CONSENT: 131 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: 132 return CONFIRMATION_DIALOG; 133 134 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY: 135 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN: 136 return DISPLAY_PASSKEY_DIALOG; 137 138 default: 139 return INVALID_DIALOG_TYPE; 140 } 141 } 142 143 /** 144 * @return - A string containing the name provided by the device. 145 */ 146 public String getDeviceName() { 147 return mDeviceName; 148 } 149 150 /** 151 * A method for querying if the bluetooth device has a profile already set up on this device. 152 * 153 * @return - A boolean indicating if the device has previous knowledge of a profile for this 154 * device. 155 */ 156 public boolean isProfileReady() { 157 return mPbapClientProfile != null && mPbapClientProfile.isProfileReady(); 158 } 159 160 /** 161 * A method for querying if the bluetooth device has access to contacts on the device. 162 * 163 * @return - A boolean indicating if the bluetooth device has permission to access the device 164 * contacts 165 */ 166 public boolean getContactSharingState() { 167 switch (mDevice.getPhonebookAccessPermission()) { 168 case BluetoothDevice.ACCESS_ALLOWED: 169 return true; 170 case BluetoothDevice.ACCESS_REJECTED: 171 return false; 172 default: 173 if (mDevice.getBluetoothClass().getDeviceClass() 174 == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) { 175 return true; 176 } 177 return false; 178 } 179 } 180 181 /** 182 * Update Phone book permission 183 * 184 */ 185 public void setContactSharingState() { 186 if ((mDevice.getPhonebookAccessPermission() != BluetoothDevice.ACCESS_ALLOWED) 187 && (mDevice.getPhonebookAccessPermission() != BluetoothDevice.ACCESS_REJECTED)) { 188 if (mDevice.getBluetoothClass().getDeviceClass() 189 == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE) { 190 onCheckedChanged(null, true); 191 } else { 192 onCheckedChanged(null, false); 193 } 194 } 195 } 196 197 /** 198 * A method for querying if the provided editable is a valid passkey/pin format for this device. 199 * 200 * @param s - The passkey/pin 201 * @return - A boolean indicating if the passkey/pin is of the correct format. 202 */ 203 public boolean isPasskeyValid(Editable s) { 204 boolean requires16Digits = mType == BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS; 205 return s.length() >= 16 && requires16Digits || s.length() > 0 && !requires16Digits; 206 } 207 208 /** 209 * A method for querying what message should be shown to the user as additional text in the 210 * dialog for this device. Returns -1 to indicate a device type that does not use this message. 211 * 212 * @return - The message ID to show the user. 213 */ 214 public int getDeviceVariantMessageId() { 215 switch (mType) { 216 case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS: 217 case BluetoothDevice.PAIRING_VARIANT_PIN: 218 return R.string.bluetooth_enter_pin_other_device; 219 220 case BluetoothDevice.PAIRING_VARIANT_PASSKEY: 221 return R.string.bluetooth_enter_passkey_other_device; 222 223 default: 224 return INVALID_DIALOG_TYPE; 225 } 226 } 227 228 /** 229 * A method for querying what message hint should be shown to the user as additional text in the 230 * dialog for this device. Returns -1 to indicate a device type that does not use this message. 231 * 232 * @return - The message ID to show the user. 233 */ 234 public int getDeviceVariantMessageHintId() { 235 switch (mType) { 236 case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS: 237 return R.string.bluetooth_pin_values_hint_16_digits; 238 239 case BluetoothDevice.PAIRING_VARIANT_PIN: 240 case BluetoothDevice.PAIRING_VARIANT_PASSKEY: 241 return R.string.bluetooth_pin_values_hint; 242 243 default: 244 return INVALID_DIALOG_TYPE; 245 } 246 } 247 248 /** 249 * A method for querying the maximum passkey/pin length for this device. 250 * 251 * @return - An int indicating the maximum length 252 */ 253 public int getDeviceMaxPasskeyLength() { 254 switch (mType) { 255 case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS: 256 case BluetoothDevice.PAIRING_VARIANT_PIN: 257 return BLUETOOTH_PIN_MAX_LENGTH; 258 259 case BluetoothDevice.PAIRING_VARIANT_PASSKEY: 260 return BLUETOOTH_PASSKEY_MAX_LENGTH; 261 262 default: 263 return 0; 264 } 265 266 } 267 268 /** 269 * A method for querying if the device uses an alphanumeric passkey. 270 * 271 * @return - a boolean indicating if the passkey can be alphanumeric. 272 */ 273 public boolean pairingCodeIsAlphanumeric() { 274 switch (mType) { 275 case BluetoothDevice.PAIRING_VARIANT_PASSKEY: 276 return false; 277 278 default: 279 return true; 280 } 281 } 282 283 /** 284 * A method used by the dialogfragment to notify the controller that the dialog has been 285 * displayed for bluetooth device types that just care about it being displayed. 286 */ 287 protected void notifyDialogDisplayed() { 288 // send an OK to the framework, indicating that the dialog has been displayed. 289 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) { 290 mDevice.setPairingConfirmation(true); 291 } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) { 292 byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPasskeyFormatted); 293 mDevice.setPin(pinBytes); 294 } 295 } 296 297 /** 298 * A method for querying if this bluetooth device type has a key it would like displayed 299 * to the user. 300 * 301 * @return - A boolean indicating if a key exists which should be displayed to the user. 302 */ 303 public boolean isDisplayPairingKeyVariant() { 304 switch (mType) { 305 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY: 306 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN: 307 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: 308 return true; 309 default: 310 return false; 311 } 312 } 313 314 /** 315 * A method for querying if this bluetooth device type has other content it would like displayed 316 * to the user. 317 * 318 * @return - A boolean indicating if content exists which should be displayed to the user. 319 */ 320 public boolean hasPairingContent() { 321 switch (mType) { 322 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY: 323 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN: 324 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: 325 return true; 326 327 default: 328 return false; 329 } 330 } 331 332 /** 333 * A method for obtaining any additional content this bluetooth device has for displaying to the 334 * user. 335 * 336 * @return - A string containing the additional content, null if none exists. 337 * @see {@link BluetoothPairingController#hasPairingContent()} 338 */ 339 public String getPairingContent() { 340 if (hasPairingContent()) { 341 return mPasskeyFormatted; 342 } else { 343 return null; 344 } 345 } 346 347 /** 348 * A method that exists to allow the fragment to update the controller with input the user has 349 * provided in the fragment. 350 * 351 * @param input - A string containing the user input. 352 */ 353 protected void updateUserInput(String input) { 354 mUserInput = input; 355 } 356 357 /** 358 * Returns the provided passkey in a format that this device expects. Only works for numeric 359 * passkeys/pins. 360 * 361 * @param passkey - An integer containing the passkey to format. 362 * @return - A string containing the formatted passkey/pin 363 */ 364 private String formatKey(int passkey) { 365 switch (mType) { 366 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: 367 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY: 368 return String.format(Locale.US, "%06d", passkey); 369 370 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN: 371 return String.format("%04d", passkey); 372 373 default: 374 return null; 375 } 376 } 377 378 /** 379 * handles the necessary communication with the bluetooth device to establish a successful 380 * pairing 381 * 382 * @param passkey - The passkey we will attempt to pair to the device with. 383 */ 384 private void onPair(String passkey) { 385 Log.d(TAG, "Pairing dialog accepted"); 386 switch (mType) { 387 case BluetoothDevice.PAIRING_VARIANT_PIN: 388 case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS: 389 byte[] pinBytes = BluetoothDevice.convertPinToBytes(passkey); 390 if (pinBytes == null) { 391 return; 392 } 393 mDevice.setPin(pinBytes); 394 break; 395 396 case BluetoothDevice.PAIRING_VARIANT_PASSKEY: 397 int pass = Integer.parseInt(passkey); 398 mDevice.setPasskey(pass); 399 break; 400 401 case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: 402 case BluetoothDevice.PAIRING_VARIANT_CONSENT: 403 mDevice.setPairingConfirmation(true); 404 break; 405 406 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY: 407 case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN: 408 // Do nothing. 409 break; 410 411 case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: 412 mDevice.setRemoteOutOfBandData(); 413 break; 414 415 default: 416 Log.e(TAG, "Incorrect pairing type received"); 417 } 418 } 419 420 /** 421 * A method for properly ending communication with the bluetooth device. Will be called by the 422 * {@link BluetoothPairingDialogFragment} when it is dismissed. 423 */ 424 public void onCancel() { 425 Log.d(TAG, "Pairing dialog canceled"); 426 mDevice.cancelPairingUserInput(); 427 } 428 429 /** 430 * A method for checking if this device is equal to another device. 431 * 432 * @param device - The other device being compared to this device. 433 * @return - A boolean indicating if the devices were equal. 434 */ 435 public boolean deviceEquals(BluetoothDevice device) { 436 return mDevice == device; 437 } 438 } 439