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