Move non-ui bt settings code to SettingsLib
Mostly this is moving classes from Settings to SettingsLib but there
were a few changes to support this separation.
- A bunch of things became public rather than package
- Moved some settings only code out of these classes
- Added error callback to handle errors
To see the changes from original classes view the diff against
patch-set 1.
Bug: 19180466
Change-Id: I69fd888362c6dbb325f6113b32c4b15cc6a23a41
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
new file mode 100755
index 0000000..ddcc49f7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -0,0 +1,787 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.ParcelUuid;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Log;
+import android.bluetooth.BluetoothAdapter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * CachedBluetoothDevice represents a remote Bluetooth device. It contains
+ * attributes of the device (such as the address, name, RSSI, etc.) and
+ * functionality that can be performed on the device (connect, pair, disconnect,
+ * etc.).
+ */
+public final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
+ private static final String TAG = "CachedBluetoothDevice";
+ private static final boolean DEBUG = Utils.V;
+
+ private final Context mContext;
+ private final LocalBluetoothAdapter mLocalAdapter;
+ private final LocalBluetoothProfileManager mProfileManager;
+ private final BluetoothDevice mDevice;
+ private String mName;
+ private short mRssi;
+ private BluetoothClass mBtClass;
+ private HashMap<LocalBluetoothProfile, Integer> mProfileConnectionState;
+
+ private final List<LocalBluetoothProfile> mProfiles =
+ new ArrayList<LocalBluetoothProfile>();
+
+ // List of profiles that were previously in mProfiles, but have been removed
+ private final List<LocalBluetoothProfile> mRemovedProfiles =
+ new ArrayList<LocalBluetoothProfile>();
+
+ // Device supports PANU but not NAP: remove PanProfile after device disconnects from NAP
+ private boolean mLocalNapRoleConnected;
+
+ private boolean mVisible;
+
+ private int mPhonebookPermissionChoice;
+
+ private int mMessagePermissionChoice;
+
+ private int mMessageRejectionCount;
+
+ private final Collection<Callback> mCallbacks = new ArrayList<Callback>();
+
+ // Following constants indicate the user's choices of Phone book/message access settings
+ // User hasn't made any choice or settings app has wiped out the memory
+ public final static int ACCESS_UNKNOWN = 0;
+ // User has accepted the connection and let Settings app remember the decision
+ public final static int ACCESS_ALLOWED = 1;
+ // User has rejected the connection and let Settings app remember the decision
+ public final static int ACCESS_REJECTED = 2;
+
+ // How many times user should reject the connection to make the choice persist.
+ private final static int MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST = 2;
+
+ private final static String MESSAGE_REJECTION_COUNT_PREFS_NAME = "bluetooth_message_reject";
+
+ /**
+ * When we connect to multiple profiles, we only want to display a single
+ * error even if they all fail. This tracks that state.
+ */
+ private boolean mIsConnectingErrorPossible;
+
+ /**
+ * Last time a bt profile auto-connect was attempted.
+ * If an ACTION_UUID intent comes in within
+ * MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect
+ * again with the new UUIDs
+ */
+ private long mConnectAttempted;
+
+ // See mConnectAttempted
+ private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000;
+
+ /** Auto-connect after pairing only if locally initiated. */
+ private boolean mConnectAfterPairing;
+
+ /**
+ * Describes the current device and profile for logging.
+ *
+ * @param profile Profile to describe
+ * @return Description of the device and profile
+ */
+ private String describe(LocalBluetoothProfile profile) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Address:").append(mDevice);
+ if (profile != null) {
+ sb.append(" Profile:").append(profile);
+ }
+
+ return sb.toString();
+ }
+
+ void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) {
+ if (Utils.D) {
+ Log.d(TAG, "onProfileStateChanged: profile " + profile +
+ " newProfileState " + newProfileState);
+ }
+ if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF)
+ {
+ if (Utils.D) Log.d(TAG, " BT Turninig Off...Profile conn state change ignored...");
+ return;
+ }
+ mProfileConnectionState.put(profile, newProfileState);
+ if (newProfileState == BluetoothProfile.STATE_CONNECTED) {
+ if (profile instanceof MapProfile) {
+ profile.setPreferred(mDevice, true);
+ } else if (!mProfiles.contains(profile)) {
+ mRemovedProfiles.remove(profile);
+ mProfiles.add(profile);
+ if (profile instanceof PanProfile &&
+ ((PanProfile) profile).isLocalRoleNap(mDevice)) {
+ // Device doesn't support NAP, so remove PanProfile on disconnect
+ mLocalNapRoleConnected = true;
+ }
+ }
+ } else if (profile instanceof MapProfile &&
+ newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
+ profile.setPreferred(mDevice, false);
+ } else if (mLocalNapRoleConnected && profile instanceof PanProfile &&
+ ((PanProfile) profile).isLocalRoleNap(mDevice) &&
+ newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
+ Log.d(TAG, "Removing PanProfile from device after NAP disconnect");
+ mProfiles.remove(profile);
+ mRemovedProfiles.add(profile);
+ mLocalNapRoleConnected = false;
+ }
+ }
+
+ CachedBluetoothDevice(Context context,
+ LocalBluetoothAdapter adapter,
+ LocalBluetoothProfileManager profileManager,
+ BluetoothDevice device) {
+ mContext = context;
+ mLocalAdapter = adapter;
+ mProfileManager = profileManager;
+ mDevice = device;
+ mProfileConnectionState = new HashMap<LocalBluetoothProfile, Integer>();
+ fillData();
+ }
+
+ public void disconnect() {
+ for (LocalBluetoothProfile profile : mProfiles) {
+ disconnect(profile);
+ }
+ // Disconnect PBAP server in case its connected
+ // This is to ensure all the profiles are disconnected as some CK/Hs do not
+ // disconnect PBAP connection when HF connection is brought down
+ PbapServerProfile PbapProfile = mProfileManager.getPbapProfile();
+ if (PbapProfile.getConnectionStatus(mDevice) == BluetoothProfile.STATE_CONNECTED)
+ {
+ PbapProfile.disconnect(mDevice);
+ }
+ }
+
+ public void disconnect(LocalBluetoothProfile profile) {
+ if (profile.disconnect(mDevice)) {
+ if (Utils.D) {
+ Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile));
+ }
+ }
+ }
+
+ public void connect(boolean connectAllProfiles) {
+ if (!ensurePaired()) {
+ return;
+ }
+
+ mConnectAttempted = SystemClock.elapsedRealtime();
+ connectWithoutResettingTimer(connectAllProfiles);
+ }
+
+ void onBondingDockConnect() {
+ // Attempt to connect if UUIDs are available. Otherwise,
+ // we will connect when the ACTION_UUID intent arrives.
+ connect(false);
+ }
+
+ private void connectWithoutResettingTimer(boolean connectAllProfiles) {
+ // Try to initialize the profiles if they were not.
+ if (mProfiles.isEmpty()) {
+ // if mProfiles is empty, then do not invoke updateProfiles. This causes a race
+ // condition with carkits during pairing, wherein RemoteDevice.UUIDs have been updated
+ // from bluetooth stack but ACTION.uuid is not sent yet.
+ // Eventually ACTION.uuid will be received which shall trigger the connection of the
+ // various profiles
+ // If UUIDs are not available yet, connect will be happen
+ // upon arrival of the ACTION_UUID intent.
+ Log.d(TAG, "No profiles. Maybe we will connect later");
+ return;
+ }
+
+ // Reset the only-show-one-error-dialog tracking variable
+ mIsConnectingErrorPossible = true;
+
+ int preferredProfiles = 0;
+ for (LocalBluetoothProfile profile : mProfiles) {
+ if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {
+ if (profile.isPreferred(mDevice)) {
+ ++preferredProfiles;
+ connectInt(profile);
+ }
+ }
+ }
+ if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
+
+ if (preferredProfiles == 0) {
+ connectAutoConnectableProfiles();
+ }
+ }
+
+ private void connectAutoConnectableProfiles() {
+ if (!ensurePaired()) {
+ return;
+ }
+ // Reset the only-show-one-error-dialog tracking variable
+ mIsConnectingErrorPossible = true;
+
+ for (LocalBluetoothProfile profile : mProfiles) {
+ if (profile.isAutoConnectable()) {
+ profile.setPreferred(mDevice, true);
+ connectInt(profile);
+ }
+ }
+ }
+
+ /**
+ * Connect this device to the specified profile.
+ *
+ * @param profile the profile to use with the remote device
+ */
+ public void connectProfile(LocalBluetoothProfile profile) {
+ mConnectAttempted = SystemClock.elapsedRealtime();
+ // Reset the only-show-one-error-dialog tracking variable
+ mIsConnectingErrorPossible = true;
+ connectInt(profile);
+ // Refresh the UI based on profile.connect() call
+ refresh();
+ }
+
+ synchronized void connectInt(LocalBluetoothProfile profile) {
+ if (!ensurePaired()) {
+ return;
+ }
+ if (profile.connect(mDevice)) {
+ if (Utils.D) {
+ Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile));
+ }
+ return;
+ }
+ Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName);
+ }
+
+ private boolean ensurePaired() {
+ if (getBondState() == BluetoothDevice.BOND_NONE) {
+ startPairing();
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public boolean startPairing() {
+ // Pairing is unreliable while scanning, so cancel discovery
+ if (mLocalAdapter.isDiscovering()) {
+ mLocalAdapter.cancelDiscovery();
+ }
+
+ if (!mDevice.createBond()) {
+ return false;
+ }
+
+ mConnectAfterPairing = true; // auto-connect after pairing
+ return true;
+ }
+
+ /**
+ * Return true if user initiated pairing on this device. The message text is
+ * slightly different for local vs. remote initiated pairing dialogs.
+ */
+ boolean isUserInitiatedPairing() {
+ return mConnectAfterPairing;
+ }
+
+ public void unpair() {
+ int state = getBondState();
+
+ if (state == BluetoothDevice.BOND_BONDING) {
+ mDevice.cancelBondProcess();
+ }
+
+ if (state != BluetoothDevice.BOND_NONE) {
+ final BluetoothDevice dev = mDevice;
+ if (dev != null) {
+ final boolean successful = dev.removeBond();
+ if (successful) {
+ if (Utils.D) {
+ Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null));
+ }
+ } else if (Utils.V) {
+ Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " +
+ describe(null));
+ }
+ }
+ }
+ }
+
+ public int getProfileConnectionState(LocalBluetoothProfile profile) {
+ if (mProfileConnectionState == null ||
+ mProfileConnectionState.get(profile) == null) {
+ // If cache is empty make the binder call to get the state
+ int state = profile.getConnectionStatus(mDevice);
+ mProfileConnectionState.put(profile, state);
+ }
+ return mProfileConnectionState.get(profile);
+ }
+
+ public void clearProfileConnectionState ()
+ {
+ if (Utils.D) {
+ Log.d(TAG," Clearing all connection state for dev:" + mDevice.getName());
+ }
+ for (LocalBluetoothProfile profile :getProfiles()) {
+ mProfileConnectionState.put(profile, BluetoothProfile.STATE_DISCONNECTED);
+ }
+ }
+
+ // TODO: do any of these need to run async on a background thread?
+ private void fillData() {
+ fetchName();
+ fetchBtClass();
+ updateProfiles();
+ migratePhonebookPermissionChoice();
+ migrateMessagePermissionChoice();
+ fetchMessageRejectionCount();
+
+ mVisible = false;
+ dispatchAttributesChanged();
+ }
+
+ public BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Populate name from BluetoothDevice.ACTION_FOUND intent
+ */
+ void setNewName(String name) {
+ if (mName == null) {
+ mName = name;
+ if (mName == null || TextUtils.isEmpty(mName)) {
+ mName = mDevice.getAddress();
+ }
+ dispatchAttributesChanged();
+ }
+ }
+
+ /**
+ * user changes the device name
+ */
+ public void setName(String name) {
+ if (!mName.equals(name)) {
+ mName = name;
+ mDevice.setAlias(name);
+ dispatchAttributesChanged();
+ }
+ }
+
+ void refreshName() {
+ fetchName();
+ dispatchAttributesChanged();
+ }
+
+ private void fetchName() {
+ mName = mDevice.getAliasName();
+
+ if (TextUtils.isEmpty(mName)) {
+ mName = mDevice.getAddress();
+ if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName);
+ }
+ }
+
+ void refresh() {
+ dispatchAttributesChanged();
+ }
+
+ public boolean isVisible() {
+ return mVisible;
+ }
+
+ public void setVisible(boolean visible) {
+ if (mVisible != visible) {
+ mVisible = visible;
+ dispatchAttributesChanged();
+ }
+ }
+
+ public int getBondState() {
+ return mDevice.getBondState();
+ }
+
+ void setRssi(short rssi) {
+ if (mRssi != rssi) {
+ mRssi = rssi;
+ dispatchAttributesChanged();
+ }
+ }
+
+ /**
+ * Checks whether we are connected to this device (any profile counts).
+ *
+ * @return Whether it is connected.
+ */
+ public boolean isConnected() {
+ for (LocalBluetoothProfile profile : mProfiles) {
+ int status = getProfileConnectionState(profile);
+ if (status == BluetoothProfile.STATE_CONNECTED) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean isConnectedProfile(LocalBluetoothProfile profile) {
+ int status = getProfileConnectionState(profile);
+ return status == BluetoothProfile.STATE_CONNECTED;
+
+ }
+
+ public boolean isBusy() {
+ for (LocalBluetoothProfile profile : mProfiles) {
+ int status = getProfileConnectionState(profile);
+ if (status == BluetoothProfile.STATE_CONNECTING
+ || status == BluetoothProfile.STATE_DISCONNECTING) {
+ return true;
+ }
+ }
+ return getBondState() == BluetoothDevice.BOND_BONDING;
+ }
+
+ /**
+ * Fetches a new value for the cached BT class.
+ */
+ private void fetchBtClass() {
+ mBtClass = mDevice.getBluetoothClass();
+ }
+
+ private boolean updateProfiles() {
+ ParcelUuid[] uuids = mDevice.getUuids();
+ if (uuids == null) return false;
+
+ ParcelUuid[] localUuids = mLocalAdapter.getUuids();
+ if (localUuids == null) return false;
+
+ /**
+ * Now we know if the device supports PBAP, update permissions...
+ */
+ processPhonebookAccess();
+
+ mProfileManager.updateProfiles(uuids, localUuids, mProfiles, mRemovedProfiles,
+ mLocalNapRoleConnected, mDevice);
+
+ if (DEBUG) {
+ Log.e(TAG, "updating profiles for " + mDevice.getAliasName());
+ BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
+
+ if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString());
+ Log.v(TAG, "UUID:");
+ for (ParcelUuid uuid : uuids) {
+ Log.v(TAG, " " + uuid);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Refreshes the UI for the BT class, including fetching the latest value
+ * for the class.
+ */
+ void refreshBtClass() {
+ fetchBtClass();
+ dispatchAttributesChanged();
+ }
+
+ /**
+ * Refreshes the UI when framework alerts us of a UUID change.
+ */
+ void onUuidChanged() {
+ updateProfiles();
+
+ if (DEBUG) {
+ Log.e(TAG, "onUuidChanged: Time since last connect"
+ + (SystemClock.elapsedRealtime() - mConnectAttempted));
+ }
+
+ /*
+ * If a connect was attempted earlier without any UUID, we will do the
+ * connect now.
+ */
+ if (!mProfiles.isEmpty()
+ && (mConnectAttempted + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock
+ .elapsedRealtime()) {
+ connectWithoutResettingTimer(false);
+ }
+ dispatchAttributesChanged();
+ }
+
+ void onBondingStateChanged(int bondState) {
+ if (bondState == BluetoothDevice.BOND_NONE) {
+ mProfiles.clear();
+ mConnectAfterPairing = false; // cancel auto-connect
+ setPhonebookPermissionChoice(ACCESS_UNKNOWN);
+ setMessagePermissionChoice(ACCESS_UNKNOWN);
+ mMessageRejectionCount = 0;
+ saveMessageRejectionCount();
+ }
+
+ refresh();
+
+ if (bondState == BluetoothDevice.BOND_BONDED) {
+ if (mDevice.isBluetoothDock()) {
+ onBondingDockConnect();
+ } else if (mConnectAfterPairing) {
+ connect(false);
+ }
+ mConnectAfterPairing = false;
+ }
+ }
+
+ void setBtClass(BluetoothClass btClass) {
+ if (btClass != null && mBtClass != btClass) {
+ mBtClass = btClass;
+ dispatchAttributesChanged();
+ }
+ }
+
+ public BluetoothClass getBtClass() {
+ return mBtClass;
+ }
+
+ public List<LocalBluetoothProfile> getProfiles() {
+ return Collections.unmodifiableList(mProfiles);
+ }
+
+ public List<LocalBluetoothProfile> getConnectableProfiles() {
+ List<LocalBluetoothProfile> connectableProfiles =
+ new ArrayList<LocalBluetoothProfile>();
+ for (LocalBluetoothProfile profile : mProfiles) {
+ if (profile.isConnectable()) {
+ connectableProfiles.add(profile);
+ }
+ }
+ return connectableProfiles;
+ }
+
+ public List<LocalBluetoothProfile> getRemovedProfiles() {
+ return mRemovedProfiles;
+ }
+
+ public void registerCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ public void unregisterCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(callback);
+ }
+ }
+
+ private void dispatchAttributesChanged() {
+ synchronized (mCallbacks) {
+ for (Callback callback : mCallbacks) {
+ callback.onDeviceAttributesChanged();
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mDevice.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ((o == null) || !(o instanceof CachedBluetoothDevice)) {
+ return false;
+ }
+ return mDevice.equals(((CachedBluetoothDevice) o).mDevice);
+ }
+
+ @Override
+ public int hashCode() {
+ return mDevice.getAddress().hashCode();
+ }
+
+ // This comparison uses non-final fields so the sort order may change
+ // when device attributes change (such as bonding state). Settings
+ // will completely refresh the device list when this happens.
+ public int compareTo(CachedBluetoothDevice another) {
+ // Connected above not connected
+ int comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0);
+ if (comparison != 0) return comparison;
+
+ // Paired above not paired
+ comparison = (another.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) -
+ (getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0);
+ if (comparison != 0) return comparison;
+
+ // Visible above not visible
+ comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0);
+ if (comparison != 0) return comparison;
+
+ // Stronger signal above weaker signal
+ comparison = another.mRssi - mRssi;
+ if (comparison != 0) return comparison;
+
+ // Fallback on name
+ return mName.compareTo(another.mName);
+ }
+
+ public interface Callback {
+ void onDeviceAttributesChanged();
+ }
+
+ public int getPhonebookPermissionChoice() {
+ int permission = mDevice.getPhonebookAccessPermission();
+ if (permission == BluetoothDevice.ACCESS_ALLOWED) {
+ return ACCESS_ALLOWED;
+ } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
+ return ACCESS_REJECTED;
+ }
+ return ACCESS_UNKNOWN;
+ }
+
+ public void setPhonebookPermissionChoice(int permissionChoice) {
+ int permission = BluetoothDevice.ACCESS_UNKNOWN;
+ if (permissionChoice == ACCESS_ALLOWED) {
+ permission = BluetoothDevice.ACCESS_ALLOWED;
+ } else if (permissionChoice == ACCESS_REJECTED) {
+ permission = BluetoothDevice.ACCESS_REJECTED;
+ }
+ mDevice.setPhonebookAccessPermission(permission);
+ }
+
+ // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
+ // app's shared preferences).
+ private void migratePhonebookPermissionChoice() {
+ SharedPreferences preferences = mContext.getSharedPreferences(
+ "bluetooth_phonebook_permission", Context.MODE_PRIVATE);
+ if (!preferences.contains(mDevice.getAddress())) {
+ return;
+ }
+
+ if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
+ int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN);
+ if (oldPermission == ACCESS_ALLOWED) {
+ mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+ } else if (oldPermission == ACCESS_REJECTED) {
+ mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+ }
+ }
+
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.remove(mDevice.getAddress());
+ editor.commit();
+ }
+
+ public int getMessagePermissionChoice() {
+ int permission = mDevice.getMessageAccessPermission();
+ if (permission == BluetoothDevice.ACCESS_ALLOWED) {
+ return ACCESS_ALLOWED;
+ } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
+ return ACCESS_REJECTED;
+ }
+ return ACCESS_UNKNOWN;
+ }
+
+ public void setMessagePermissionChoice(int permissionChoice) {
+ int permission = BluetoothDevice.ACCESS_UNKNOWN;
+ if (permissionChoice == ACCESS_ALLOWED) {
+ permission = BluetoothDevice.ACCESS_ALLOWED;
+ } else if (permissionChoice == ACCESS_REJECTED) {
+ permission = BluetoothDevice.ACCESS_REJECTED;
+ }
+ mDevice.setMessageAccessPermission(permission);
+ }
+
+ // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
+ // app's shared preferences).
+ private void migrateMessagePermissionChoice() {
+ SharedPreferences preferences = mContext.getSharedPreferences(
+ "bluetooth_message_permission", Context.MODE_PRIVATE);
+ if (!preferences.contains(mDevice.getAddress())) {
+ return;
+ }
+
+ if (mDevice.getMessageAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
+ int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN);
+ if (oldPermission == ACCESS_ALLOWED) {
+ mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+ } else if (oldPermission == ACCESS_REJECTED) {
+ mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+ }
+ }
+
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.remove(mDevice.getAddress());
+ editor.commit();
+ }
+
+ /**
+ * @return Whether this rejection should persist.
+ */
+ public boolean checkAndIncreaseMessageRejectionCount() {
+ if (mMessageRejectionCount < MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST) {
+ mMessageRejectionCount++;
+ saveMessageRejectionCount();
+ }
+ return mMessageRejectionCount >= MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST;
+ }
+
+ private void fetchMessageRejectionCount() {
+ SharedPreferences preference = mContext.getSharedPreferences(
+ MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE);
+ mMessageRejectionCount = preference.getInt(mDevice.getAddress(), 0);
+ }
+
+ private void saveMessageRejectionCount() {
+ SharedPreferences.Editor editor = mContext.getSharedPreferences(
+ MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE).edit();
+ if (mMessageRejectionCount == 0) {
+ editor.remove(mDevice.getAddress());
+ } else {
+ editor.putInt(mDevice.getAddress(), mMessageRejectionCount);
+ }
+ editor.commit();
+ }
+
+ private void processPhonebookAccess() {
+ if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) return;
+
+ ParcelUuid[] uuids = mDevice.getUuids();
+ if (BluetoothUuid.containsAnyUuid(uuids, PbapServerProfile.PBAB_CLIENT_UUIDS)) {
+ // The pairing dialog now warns of phone-book access for paired devices.
+ // No separate prompt is displayed after pairing.
+ setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
+ }
+ }
+}