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.bluetooth.BluetoothClass; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothProfile; 22 import android.content.Context; 23 import android.content.SharedPreferences; 24 import android.os.ParcelUuid; 25 import android.os.SystemClock; 26 import android.text.TextUtils; 27 import android.util.Log; 28 import android.bluetooth.BluetoothAdapter; 29 30 import java.util.ArrayList; 31 import java.util.Collection; 32 import java.util.Collections; 33 import java.util.HashMap; 34 import java.util.List; 35 36 /** 37 * CachedBluetoothDevice represents a remote Bluetooth device. It contains 38 * attributes of the device (such as the address, name, RSSI, etc.) and 39 * functionality that can be performed on the device (connect, pair, disconnect, 40 * etc.). 41 */ 42 final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> { 43 private static final String TAG = "CachedBluetoothDevice"; 44 private static final boolean DEBUG = Utils.V; 45 46 private final Context mContext; 47 private final LocalBluetoothAdapter mLocalAdapter; 48 private final LocalBluetoothProfileManager mProfileManager; 49 private final BluetoothDevice mDevice; 50 private String mName; 51 private short mRssi; 52 private BluetoothClass mBtClass; 53 private HashMap<LocalBluetoothProfile, Integer> mProfileConnectionState; 54 55 private final List<LocalBluetoothProfile> mProfiles = 56 new ArrayList<LocalBluetoothProfile>(); 57 58 // List of profiles that were previously in mProfiles, but have been removed 59 private final List<LocalBluetoothProfile> mRemovedProfiles = 60 new ArrayList<LocalBluetoothProfile>(); 61 62 // Device supports PANU but not NAP: remove PanProfile after device disconnects from NAP 63 private boolean mLocalNapRoleConnected; 64 65 private boolean mVisible; 66 67 private int mPhonebookPermissionChoice; 68 69 private int mMessagePermissionChoice; 70 71 private int mPhonebookRejectedTimes; 72 73 private int mMessageRejectedTimes; 74 75 private final Collection<Callback> mCallbacks = new ArrayList<Callback>(); 76 77 // Following constants indicate the user's choices of Phone book/message access settings 78 // User hasn't made any choice or settings app has wiped out the memory 79 final static int ACCESS_UNKNOWN = 0; 80 // User has accepted the connection and let Settings app remember the decision 81 final static int ACCESS_ALLOWED = 1; 82 // User has rejected the connection and let Settings app remember the decision 83 final static int ACCESS_REJECTED = 2; 84 85 // how many times did User reject the connection to make the rejected persist. 86 final static int PERSIST_REJECTED_TIMES_LIMIT = 2; 87 88 private final static String PHONEBOOK_PREFS_NAME = "bluetooth_phonebook_permission"; 89 private final static String MESSAGE_PREFS_NAME = "bluetooth_message_permission"; 90 private final static String PHONEBOOK_REJECT_TIMES = "bluetooth_phonebook_reject"; 91 private final static String MESSAGE_REJECT_TIMES = "bluetooth_message_reject"; 92 93 /** 94 * When we connect to multiple profiles, we only want to display a single 95 * error even if they all fail. This tracks that state. 96 */ 97 private boolean mIsConnectingErrorPossible; 98 99 /** 100 * Last time a bt profile auto-connect was attempted. 101 * If an ACTION_UUID intent comes in within 102 * MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect 103 * again with the new UUIDs 104 */ 105 private long mConnectAttempted; 106 107 // See mConnectAttempted 108 private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000; 109 110 /** Auto-connect after pairing only if locally initiated. */ 111 private boolean mConnectAfterPairing; 112 113 /** 114 * Describes the current device and profile for logging. 115 * 116 * @param profile Profile to describe 117 * @return Description of the device and profile 118 */ 119 private String describe(LocalBluetoothProfile profile) { 120 StringBuilder sb = new StringBuilder(); 121 sb.append("Address:").append(mDevice); 122 if (profile != null) { 123 sb.append(" Profile:").append(profile); 124 } 125 126 return sb.toString(); 127 } 128 129 void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) { 130 if (Utils.D) { 131 Log.d(TAG, "onProfileStateChanged: profile " + profile + 132 " newProfileState " + newProfileState); 133 } 134 if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF) 135 { 136 if (Utils.D) Log.d(TAG, " BT Turninig Off...Profile conn state change ignored..."); 137 return; 138 } 139 mProfileConnectionState.put(profile, newProfileState); 140 if (newProfileState == BluetoothProfile.STATE_CONNECTED) { 141 if (!mProfiles.contains(profile)) { 142 mRemovedProfiles.remove(profile); 143 mProfiles.add(profile); 144 if (profile instanceof PanProfile && 145 ((PanProfile) profile).isLocalRoleNap(mDevice)) { 146 // Device doesn't support NAP, so remove PanProfile on disconnect 147 mLocalNapRoleConnected = true; 148 } 149 } 150 if (profile instanceof MapProfile) { 151 profile.setPreferred(mDevice, true); 152 refresh(); 153 } 154 } else if (profile instanceof MapProfile && 155 newProfileState == BluetoothProfile.STATE_DISCONNECTED) { 156 if (mProfiles.contains(profile)) { 157 mRemovedProfiles.add(profile); 158 mProfiles.remove(profile); 159 } 160 profile.setPreferred(mDevice, false); 161 refresh(); 162 } else if (mLocalNapRoleConnected && profile instanceof PanProfile && 163 ((PanProfile) profile).isLocalRoleNap(mDevice) && 164 newProfileState == BluetoothProfile.STATE_DISCONNECTED) { 165 Log.d(TAG, "Removing PanProfile from device after NAP disconnect"); 166 mProfiles.remove(profile); 167 mRemovedProfiles.add(profile); 168 mLocalNapRoleConnected = false; 169 } 170 } 171 172 CachedBluetoothDevice(Context context, 173 LocalBluetoothAdapter adapter, 174 LocalBluetoothProfileManager profileManager, 175 BluetoothDevice device) { 176 mContext = context; 177 mLocalAdapter = adapter; 178 mProfileManager = profileManager; 179 mDevice = device; 180 mProfileConnectionState = new HashMap<LocalBluetoothProfile, Integer>(); 181 fillData(); 182 } 183 184 void disconnect() { 185 for (LocalBluetoothProfile profile : mProfiles) { 186 disconnect(profile); 187 } 188 // Disconnect PBAP server in case its connected 189 // This is to ensure all the profiles are disconnected as some CK/Hs do not 190 // disconnect PBAP connection when HF connection is brought down 191 PbapServerProfile PbapProfile = mProfileManager.getPbapProfile(); 192 if (PbapProfile.getConnectionStatus(mDevice) == BluetoothProfile.STATE_CONNECTED) 193 { 194 PbapProfile.disconnect(mDevice); 195 } 196 } 197 198 void disconnect(LocalBluetoothProfile profile) { 199 if (profile.disconnect(mDevice)) { 200 if (Utils.D) { 201 Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile)); 202 } 203 } 204 } 205 206 void connect(boolean connectAllProfiles) { 207 if (!ensurePaired()) { 208 return; 209 } 210 211 mConnectAttempted = SystemClock.elapsedRealtime(); 212 connectWithoutResettingTimer(connectAllProfiles); 213 } 214 215 void onBondingDockConnect() { 216 // Attempt to connect if UUIDs are available. Otherwise, 217 // we will connect when the ACTION_UUID intent arrives. 218 connect(false); 219 } 220 221 private void connectWithoutResettingTimer(boolean connectAllProfiles) { 222 // Try to initialize the profiles if they were not. 223 if (mProfiles.isEmpty()) { 224 // if mProfiles is empty, then do not invoke updateProfiles. This causes a race 225 // condition with carkits during pairing, wherein RemoteDevice.UUIDs have been updated 226 // from bluetooth stack but ACTION.uuid is not sent yet. 227 // Eventually ACTION.uuid will be received which shall trigger the connection of the 228 // various profiles 229 // If UUIDs are not available yet, connect will be happen 230 // upon arrival of the ACTION_UUID intent. 231 Log.d(TAG, "No profiles. Maybe we will connect later"); 232 return; 233 } 234 235 // Reset the only-show-one-error-dialog tracking variable 236 mIsConnectingErrorPossible = true; 237 238 int preferredProfiles = 0; 239 for (LocalBluetoothProfile profile : mProfiles) { 240 if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) { 241 if (profile.isPreferred(mDevice)) { 242 ++preferredProfiles; 243 connectInt(profile); 244 } 245 } 246 } 247 if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles); 248 249 if (preferredProfiles == 0) { 250 connectAutoConnectableProfiles(); 251 } 252 } 253 254 private void connectAutoConnectableProfiles() { 255 if (!ensurePaired()) { 256 return; 257 } 258 // Reset the only-show-one-error-dialog tracking variable 259 mIsConnectingErrorPossible = true; 260 261 for (LocalBluetoothProfile profile : mProfiles) { 262 if (profile.isAutoConnectable()) { 263 profile.setPreferred(mDevice, true); 264 connectInt(profile); 265 } 266 } 267 } 268 269 /** 270 * Connect this device to the specified profile. 271 * 272 * @param profile the profile to use with the remote device 273 */ 274 void connectProfile(LocalBluetoothProfile profile) { 275 mConnectAttempted = SystemClock.elapsedRealtime(); 276 // Reset the only-show-one-error-dialog tracking variable 277 mIsConnectingErrorPossible = true; 278 connectInt(profile); 279 // Refresh the UI based on profile.connect() call 280 refresh(); 281 } 282 283 synchronized void connectInt(LocalBluetoothProfile profile) { 284 if (!ensurePaired()) { 285 return; 286 } 287 if (profile.connect(mDevice)) { 288 if (Utils.D) { 289 Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile)); 290 } 291 return; 292 } 293 Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName); 294 } 295 296 private boolean ensurePaired() { 297 if (getBondState() == BluetoothDevice.BOND_NONE) { 298 startPairing(); 299 return false; 300 } else { 301 return true; 302 } 303 } 304 305 boolean startPairing() { 306 // Pairing is unreliable while scanning, so cancel discovery 307 if (mLocalAdapter.isDiscovering()) { 308 mLocalAdapter.cancelDiscovery(); 309 } 310 311 if (!mDevice.createBond()) { 312 return false; 313 } 314 315 mConnectAfterPairing = true; // auto-connect after pairing 316 return true; 317 } 318 319 /** 320 * Return true if user initiated pairing on this device. The message text is 321 * slightly different for local vs. remote initiated pairing dialogs. 322 */ 323 boolean isUserInitiatedPairing() { 324 return mConnectAfterPairing; 325 } 326 327 void unpair() { 328 int state = getBondState(); 329 330 if (state == BluetoothDevice.BOND_BONDING) { 331 mDevice.cancelBondProcess(); 332 } 333 334 if (state != BluetoothDevice.BOND_NONE) { 335 final BluetoothDevice dev = mDevice; 336 if (dev != null) { 337 final boolean successful = dev.removeBond(); 338 if (successful) { 339 if (Utils.D) { 340 Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null)); 341 } 342 } else if (Utils.V) { 343 Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " + 344 describe(null)); 345 } 346 } 347 } 348 } 349 350 int getProfileConnectionState(LocalBluetoothProfile profile) { 351 if (mProfileConnectionState == null || 352 mProfileConnectionState.get(profile) == null) { 353 // If cache is empty make the binder call to get the state 354 int state = profile.getConnectionStatus(mDevice); 355 mProfileConnectionState.put(profile, state); 356 } 357 return mProfileConnectionState.get(profile); 358 } 359 360 public void clearProfileConnectionState () 361 { 362 if (Utils.D) { 363 Log.d(TAG," Clearing all connection state for dev:" + mDevice.getName()); 364 } 365 for (LocalBluetoothProfile profile :getProfiles()) { 366 mProfileConnectionState.put(profile, BluetoothProfile.STATE_DISCONNECTED); 367 } 368 } 369 370 // TODO: do any of these need to run async on a background thread? 371 private void fillData() { 372 fetchName(); 373 fetchBtClass(); 374 updateProfiles(); 375 fetchPhonebookPermissionChoice(); 376 fetchMessagePermissionChoice(); 377 fetchPhonebookRejectTimes(); 378 fetchMessageRejectTimes(); 379 380 mVisible = false; 381 dispatchAttributesChanged(); 382 } 383 384 BluetoothDevice getDevice() { 385 return mDevice; 386 } 387 388 String getName() { 389 return mName; 390 } 391 392 void setName(String name) { 393 if (!mName.equals(name)) { 394 if (TextUtils.isEmpty(name)) { 395 // TODO: use friendly name for unknown device (bug 1181856) 396 mName = mDevice.getAddress(); 397 } else { 398 mName = name; 399 mDevice.setAlias(name); 400 } 401 dispatchAttributesChanged(); 402 } 403 } 404 405 void refreshName() { 406 fetchName(); 407 dispatchAttributesChanged(); 408 } 409 410 private void fetchName() { 411 mName = mDevice.getAliasName(); 412 413 if (TextUtils.isEmpty(mName)) { 414 mName = mDevice.getAddress(); 415 if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName); 416 } 417 } 418 419 void refresh() { 420 dispatchAttributesChanged(); 421 } 422 423 boolean isVisible() { 424 return mVisible; 425 } 426 427 void setVisible(boolean visible) { 428 if (mVisible != visible) { 429 mVisible = visible; 430 dispatchAttributesChanged(); 431 } 432 } 433 434 int getBondState() { 435 return mDevice.getBondState(); 436 } 437 438 void setRssi(short rssi) { 439 if (mRssi != rssi) { 440 mRssi = rssi; 441 dispatchAttributesChanged(); 442 } 443 } 444 445 /** 446 * Checks whether we are connected to this device (any profile counts). 447 * 448 * @return Whether it is connected. 449 */ 450 boolean isConnected() { 451 for (LocalBluetoothProfile profile : mProfiles) { 452 int status = getProfileConnectionState(profile); 453 if (status == BluetoothProfile.STATE_CONNECTED) { 454 return true; 455 } 456 } 457 458 return false; 459 } 460 461 boolean isConnectedProfile(LocalBluetoothProfile profile) { 462 int status = getProfileConnectionState(profile); 463 return status == BluetoothProfile.STATE_CONNECTED; 464 465 } 466 467 boolean isBusy() { 468 for (LocalBluetoothProfile profile : mProfiles) { 469 int status = getProfileConnectionState(profile); 470 if (status == BluetoothProfile.STATE_CONNECTING 471 || status == BluetoothProfile.STATE_DISCONNECTING) { 472 return true; 473 } 474 } 475 return getBondState() == BluetoothDevice.BOND_BONDING; 476 } 477 478 /** 479 * Fetches a new value for the cached BT class. 480 */ 481 private void fetchBtClass() { 482 mBtClass = mDevice.getBluetoothClass(); 483 } 484 485 private boolean updateProfiles() { 486 ParcelUuid[] uuids = mDevice.getUuids(); 487 if (uuids == null) return false; 488 489 ParcelUuid[] localUuids = mLocalAdapter.getUuids(); 490 if (localUuids == null) return false; 491 492 mProfileManager.updateProfiles(uuids, localUuids, mProfiles, mRemovedProfiles, mLocalNapRoleConnected); 493 494 if (DEBUG) { 495 Log.e(TAG, "updating profiles for " + mDevice.getAliasName()); 496 BluetoothClass bluetoothClass = mDevice.getBluetoothClass(); 497 498 if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString()); 499 Log.v(TAG, "UUID:"); 500 for (ParcelUuid uuid : uuids) { 501 Log.v(TAG, " " + uuid); 502 } 503 } 504 return true; 505 } 506 507 /** 508 * Refreshes the UI for the BT class, including fetching the latest value 509 * for the class. 510 */ 511 void refreshBtClass() { 512 fetchBtClass(); 513 dispatchAttributesChanged(); 514 } 515 516 /** 517 * Refreshes the UI when framework alerts us of a UUID change. 518 */ 519 void onUuidChanged() { 520 updateProfiles(); 521 522 if (DEBUG) { 523 Log.e(TAG, "onUuidChanged: Time since last connect" 524 + (SystemClock.elapsedRealtime() - mConnectAttempted)); 525 } 526 527 /* 528 * If a connect was attempted earlier without any UUID, we will do the 529 * connect now. 530 */ 531 if (!mProfiles.isEmpty() 532 && (mConnectAttempted + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock 533 .elapsedRealtime()) { 534 connectWithoutResettingTimer(false); 535 } 536 dispatchAttributesChanged(); 537 } 538 539 void onBondingStateChanged(int bondState) { 540 if (bondState == BluetoothDevice.BOND_NONE) { 541 mProfiles.clear(); 542 mConnectAfterPairing = false; // cancel auto-connect 543 setPhonebookPermissionChoice(ACCESS_UNKNOWN); 544 setMessagePermissionChoice(ACCESS_UNKNOWN); 545 mPhonebookRejectedTimes = 0; 546 savePhonebookRejectTimes(); 547 mMessageRejectedTimes = 0; 548 saveMessageRejectTimes(); 549 } 550 551 refresh(); 552 553 if (bondState == BluetoothDevice.BOND_BONDED) { 554 if (mDevice.isBluetoothDock()) { 555 onBondingDockConnect(); 556 } else if (mConnectAfterPairing) { 557 connect(false); 558 } 559 mConnectAfterPairing = false; 560 } 561 } 562 563 void setBtClass(BluetoothClass btClass) { 564 if (btClass != null && mBtClass != btClass) { 565 mBtClass = btClass; 566 dispatchAttributesChanged(); 567 } 568 } 569 570 BluetoothClass getBtClass() { 571 return mBtClass; 572 } 573 574 List<LocalBluetoothProfile> getProfiles() { 575 return Collections.unmodifiableList(mProfiles); 576 } 577 578 List<LocalBluetoothProfile> getConnectableProfiles() { 579 List<LocalBluetoothProfile> connectableProfiles = 580 new ArrayList<LocalBluetoothProfile>(); 581 for (LocalBluetoothProfile profile : mProfiles) { 582 if (profile.isConnectable()) { 583 connectableProfiles.add(profile); 584 } 585 } 586 return connectableProfiles; 587 } 588 589 List<LocalBluetoothProfile> getRemovedProfiles() { 590 return mRemovedProfiles; 591 } 592 593 void registerCallback(Callback callback) { 594 synchronized (mCallbacks) { 595 mCallbacks.add(callback); 596 } 597 } 598 599 void unregisterCallback(Callback callback) { 600 synchronized (mCallbacks) { 601 mCallbacks.remove(callback); 602 } 603 } 604 605 private void dispatchAttributesChanged() { 606 synchronized (mCallbacks) { 607 for (Callback callback : mCallbacks) { 608 callback.onDeviceAttributesChanged(); 609 } 610 } 611 } 612 613 @Override 614 public String toString() { 615 return mDevice.toString(); 616 } 617 618 @Override 619 public boolean equals(Object o) { 620 if ((o == null) || !(o instanceof CachedBluetoothDevice)) { 621 return false; 622 } 623 return mDevice.equals(((CachedBluetoothDevice) o).mDevice); 624 } 625 626 @Override 627 public int hashCode() { 628 return mDevice.getAddress().hashCode(); 629 } 630 631 // This comparison uses non-final fields so the sort order may change 632 // when device attributes change (such as bonding state). Settings 633 // will completely refresh the device list when this happens. 634 public int compareTo(CachedBluetoothDevice another) { 635 // Connected above not connected 636 int comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0); 637 if (comparison != 0) return comparison; 638 639 // Paired above not paired 640 comparison = (another.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) - 641 (getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0); 642 if (comparison != 0) return comparison; 643 644 // Visible above not visible 645 comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0); 646 if (comparison != 0) return comparison; 647 648 // Stronger signal above weaker signal 649 comparison = another.mRssi - mRssi; 650 if (comparison != 0) return comparison; 651 652 // Fallback on name 653 return mName.compareTo(another.mName); 654 } 655 656 public interface Callback { 657 void onDeviceAttributesChanged(); 658 } 659 660 int getPhonebookPermissionChoice() { 661 return mPhonebookPermissionChoice; 662 } 663 664 void setPhonebookPermissionChoice(int permissionChoice) { 665 // if user reject it, only save it when reject exceed limit. 666 if (permissionChoice == ACCESS_REJECTED) { 667 mPhonebookRejectedTimes++; 668 savePhonebookRejectTimes(); 669 if (mPhonebookRejectedTimes < PERSIST_REJECTED_TIMES_LIMIT) { 670 return; 671 } 672 } 673 674 mPhonebookPermissionChoice = permissionChoice; 675 676 SharedPreferences.Editor editor = 677 mContext.getSharedPreferences(PHONEBOOK_PREFS_NAME, Context.MODE_PRIVATE).edit(); 678 if (permissionChoice == ACCESS_UNKNOWN) { 679 editor.remove(mDevice.getAddress()); 680 } else { 681 editor.putInt(mDevice.getAddress(), permissionChoice); 682 } 683 editor.commit(); 684 } 685 686 private void fetchPhonebookPermissionChoice() { 687 SharedPreferences preference = mContext.getSharedPreferences(PHONEBOOK_PREFS_NAME, 688 Context.MODE_PRIVATE); 689 mPhonebookPermissionChoice = preference.getInt(mDevice.getAddress(), 690 ACCESS_UNKNOWN); 691 } 692 693 private void fetchPhonebookRejectTimes() { 694 SharedPreferences preference = mContext.getSharedPreferences(PHONEBOOK_REJECT_TIMES, 695 Context.MODE_PRIVATE); 696 mPhonebookRejectedTimes = preference.getInt(mDevice.getAddress(), 0); 697 } 698 699 private void savePhonebookRejectTimes() { 700 SharedPreferences.Editor editor = 701 mContext.getSharedPreferences(PHONEBOOK_REJECT_TIMES, 702 Context.MODE_PRIVATE).edit(); 703 if (mPhonebookRejectedTimes == 0) { 704 editor.remove(mDevice.getAddress()); 705 } else { 706 editor.putInt(mDevice.getAddress(), mPhonebookRejectedTimes); 707 } 708 editor.commit(); 709 } 710 711 int getMessagePermissionChoice() { 712 return mMessagePermissionChoice; 713 } 714 715 void setMessagePermissionChoice(int permissionChoice) { 716 // if user reject it, only save it when reject exceed limit. 717 if (permissionChoice == ACCESS_REJECTED) { 718 mMessageRejectedTimes++; 719 saveMessageRejectTimes(); 720 if (mMessageRejectedTimes < PERSIST_REJECTED_TIMES_LIMIT) { 721 return; 722 } 723 } 724 725 mMessagePermissionChoice = permissionChoice; 726 727 SharedPreferences.Editor editor = 728 mContext.getSharedPreferences(MESSAGE_PREFS_NAME, Context.MODE_PRIVATE).edit(); 729 if (permissionChoice == ACCESS_UNKNOWN) { 730 editor.remove(mDevice.getAddress()); 731 } else { 732 editor.putInt(mDevice.getAddress(), permissionChoice); 733 } 734 editor.commit(); 735 } 736 737 private void fetchMessagePermissionChoice() { 738 SharedPreferences preference = mContext.getSharedPreferences(MESSAGE_PREFS_NAME, 739 Context.MODE_PRIVATE); 740 mMessagePermissionChoice = preference.getInt(mDevice.getAddress(), 741 ACCESS_UNKNOWN); 742 } 743 744 private void fetchMessageRejectTimes() { 745 SharedPreferences preference = mContext.getSharedPreferences(MESSAGE_REJECT_TIMES, 746 Context.MODE_PRIVATE); 747 mMessageRejectedTimes = preference.getInt(mDevice.getAddress(), 0); 748 } 749 750 private void saveMessageRejectTimes() { 751 SharedPreferences.Editor editor = 752 mContext.getSharedPreferences(MESSAGE_REJECT_TIMES, Context.MODE_PRIVATE).edit(); 753 if (mMessageRejectedTimes == 0) { 754 editor.remove(mDevice.getAddress()); 755 } else { 756 editor.putInt(mDevice.getAddress(), mMessageRejectedTimes); 757 } 758 editor.commit(); 759 } 760 761 } 762