OSDN Git Service

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