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