Merge "Remember user choice for phone book access permission dialog"
diff --git a/res/layout/bluetooth_pb_access.xml b/res/layout/bluetooth_pb_access.xml
index 80f78a6..24f0df2 100644
--- a/res/layout/bluetooth_pb_access.xml
+++ b/res/layout/bluetooth_pb_access.xml
@@ -36,12 +36,12 @@
             android:gravity="center_horizontal"
             android:textAppearance="?android:attr/textAppearanceMedium" />
 
-        <CheckBox android:id="@+id/bluetooth_pb_alwaysallowed"
+        <CheckBox android:id="@+id/bluetooth_pb_remember_choice"
             style="?android:attr/textAppearanceMedium"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="2dip"
-            android:text="@string/bluetooth_pb_alwaysallowed" />
+            android:text="@string/bluetooth_pb_remember_choice" />
 
     </LinearLayout>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c9bfb7f..7e73bca 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -323,7 +323,7 @@
     <string name="bluetooth_pb_acceptance_dialog_text">%1$s would like to access your contacts and call history. Give access to %2$s?</string>
 
     <!-- Bluetooth phone book permission Alert Activity checkbox text [CHAR LIMIT=none] -->
-    <string name="bluetooth_pb_alwaysallowed">Always allowed?</string>
+    <string name="bluetooth_pb_remember_choice">Don\'t ask again</string>
 
     <!-- Date & time settings screen title -->
     <string name="date_and_time">Date &amp; time settings</string>
diff --git a/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java b/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java
index 4d96140..4fd6cee 100644
--- a/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java
+++ b/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java
@@ -53,13 +53,12 @@
     private TextView messageView;
     private Button mOkButton;
     private BluetoothDevice mDevice;
-
-    private CheckBox mAlwaysAllowed;
-    private boolean mAlwaysAllowedValue = true;
-
     private String mReturnPackage = null;
     private String mReturnClass = null;
 
+    private CheckBox mRememberChoice;
+    private boolean mRememberChoiceValue = false;
+
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -70,6 +69,7 @@
             }
         }
     };
+    private boolean mReceiverRegistered = false;
 
     private void dismissDialog() {
         this.dismiss();
@@ -81,26 +81,31 @@
 
         Intent i = getIntent();
         String action = i.getAction();
+        if (!action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST)) {
+            Log.e(TAG, "Error: this activity may be started only with intent "
+                  + "ACTION_CONNECTION_ACCESS_REQUEST");
+            finish();
+            return;
+        }
+
         mDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
         mReturnPackage = i.getStringExtra(BluetoothDevice.EXTRA_PACKAGE_NAME);
         mReturnClass = i.getStringExtra(BluetoothDevice.EXTRA_CLASS_NAME);
+        int requestType = i.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
+                                     BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
 
-        if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST)) {
-            mDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-            if (i.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
-                              BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) ==
-                BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION) {
-                showConnectionDialog();
-            } else {
-                showPbapDialog();
-            }
+        if (requestType == BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION) {
+            showConnectionDialog();
+        } else if (requestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
+            showPhonebookDialog();
         } else {
-            Log.e(TAG, "Error: this activity may be started only with intent "
-                    + "ACTION_CONNECTION_ACCESS_REQUEST");
+            Log.e(TAG, "Error: bad request type: " + requestType);
             finish();
+            return;
         }
         registerReceiver(mReceiver,
                          new IntentFilter(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL));
+        mReceiverRegistered = true;
     }
 
     private void showConnectionDialog() {
@@ -116,11 +121,11 @@
         setupAlert();
     }
 
-    private void showPbapDialog() {
+    private void showPhonebookDialog() {
         final AlertController.AlertParams p = mAlertParams;
         p.mIconId = android.R.drawable.ic_dialog_info;
         p.mTitle = getString(R.string.bluetooth_phonebook_request);
-        p.mView = createPbapDialogView();
+        p.mView = createPhonebookDialogView();
         p.mPositiveButtonText = getString(android.R.string.yes);
         p.mPositiveButtonListener = this;
         p.mNegativeButtonText = getString(android.R.string.no);
@@ -138,7 +143,7 @@
         return mMessage1;
     }
 
-    private String createPbapDisplayText() {
+    private String createPhonebookDisplayText() {
         String mRemoteName = mDevice != null ? mDevice.getAliasName() : null;
 
         if (mRemoteName == null) mRemoteName = getString(R.string.unknown);
@@ -154,18 +159,18 @@
         return mView;
     }
 
-    private View createPbapDialogView() {
+    private View createPhonebookDialogView() {
         mView = getLayoutInflater().inflate(R.layout.bluetooth_pb_access, null);
         messageView = (TextView)mView.findViewById(R.id.message);
-        messageView.setText(createPbapDisplayText());
-        mAlwaysAllowed = (CheckBox)mView.findViewById(R.id.bluetooth_pb_alwaysallowed);
-        mAlwaysAllowed.setChecked(true);
-        mAlwaysAllowed.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+        messageView.setText(createPhonebookDisplayText());
+        mRememberChoice = (CheckBox)mView.findViewById(R.id.bluetooth_pb_remember_choice);
+        mRememberChoice.setChecked(false);
+        mRememberChoice.setOnCheckedChangeListener(new OnCheckedChangeListener() {
             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                 if (isChecked) {
-                    mAlwaysAllowedValue = true;
+                    mRememberChoiceValue = true;
                 } else {
-                    mAlwaysAllowedValue = false;
+                    mRememberChoiceValue = false;
                 }
             }
             });
@@ -173,14 +178,22 @@
     }
 
     private void onPositive() {
-        if (DEBUG) Log.d(TAG, "onPositive mAlwaysAllowedValue: " + mAlwaysAllowedValue);
+        if (DEBUG) Log.d(TAG, "onPositive mRememberChoiceValue: " + mRememberChoiceValue);
+
+        if (mRememberChoiceValue) {
+            savePhonebookPermissionChoice(CachedBluetoothDevice.PHONEBOOK_ACCESS_ALLOWED);
+        }
         sendIntentToReceiver(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY, true,
-                             BluetoothDevice.EXTRA_ALWAYS_ALLOWED, mAlwaysAllowedValue);
+                             BluetoothDevice.EXTRA_ALWAYS_ALLOWED, mRememberChoiceValue);
         finish();
     }
 
     private void onNegative() {
-        if (DEBUG) Log.d(TAG, "onNegative mAlwaysAllowedValue: " + mAlwaysAllowedValue);
+        if (DEBUG) Log.d(TAG, "onNegative mRememberChoiceValue: " + mRememberChoiceValue);
+
+        if (mRememberChoiceValue) {
+            savePhonebookPermissionChoice(CachedBluetoothDevice.PHONEBOOK_ACCESS_REJECTED);
+        }
         sendIntentToReceiver(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY, false,
                              null, false // dummy value, no effect since last param is null
                              );
@@ -223,10 +236,21 @@
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        unregisterReceiver(mReceiver);
+        if (mReceiverRegistered) {
+            unregisterReceiver(mReceiver);
+            mReceiverRegistered = false;
+        }
     }
 
     public boolean onPreferenceChange(Preference preference, Object newValue) {
         return true;
     }
+
+    private void savePhonebookPermissionChoice(int permissionChoice) {
+        LocalBluetoothManager bluetoothManager = LocalBluetoothManager.getInstance(this);
+        CachedBluetoothDeviceManager cachedDeviceManager =
+            bluetoothManager.getCachedDeviceManager();
+        CachedBluetoothDevice cachedDevice = cachedDeviceManager.findDevice(mDevice);
+        cachedDevice.setPhonebookPermissionChoice(permissionChoice);
+    }
 }
diff --git a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
index 51055af..e2231bb 100644
--- a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
+++ b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java
@@ -38,30 +38,44 @@
     private static final boolean DEBUG = Utils.V;
     public static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
 
+    Context mContext;
+    int mRequestType;
+    BluetoothDevice mDevice;
+    String mReturnPackage = null;
+    String mReturnClass = null;
+
     @Override
     public void onReceive(Context context, Intent intent) {
+        mContext = context;
         String action = intent.getAction();
 
         if (DEBUG) Log.d(TAG, "onReceive");
 
         if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST)) {
             // convert broadcast intent into activity intent (same action string)
-            BluetoothDevice device =
-                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-            int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
+            mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            mRequestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
                                                  BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION);
-            String returnPackage = intent.getStringExtra(BluetoothDevice.EXTRA_PACKAGE_NAME);
-            String returnClass = intent.getStringExtra(BluetoothDevice.EXTRA_CLASS_NAME);
+            mReturnPackage = intent.getStringExtra(BluetoothDevice.EXTRA_PACKAGE_NAME);
+            mReturnClass = intent.getStringExtra(BluetoothDevice.EXTRA_CLASS_NAME);
 
             Intent connectionAccessIntent = new Intent(action);
             connectionAccessIntent.setClass(context, BluetoothPermissionActivity.class);
             connectionAccessIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, requestType);
-            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, returnPackage);
-            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, returnClass);
+            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
+                                            mRequestType);
+            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, mReturnPackage);
+            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, mReturnClass);
 
-            String deviceAddress = device != null ? device.getAddress() : null;
+            // Check if user had made decisions on accepting or rejecting the phonebook access
+            // request. If there is, reply the request and return, no need to start permission
+            // activity dialog or notification.
+            if (checkUserChoice()) {
+                return;
+            }
+
+            String deviceAddress = mDevice != null ? mDevice.getAddress() : null;
 
             PowerManager powerManager =
                 (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -76,14 +90,15 @@
                 // "Clear All Notifications" button
 
                 Intent deleteIntent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
-                deleteIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+                deleteIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
                 deleteIntent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
                         BluetoothDevice.CONNECTION_ACCESS_NO);
 
-                Notification notification = new Notification(android.R.drawable.stat_sys_data_bluetooth,
+                Notification notification = new Notification(
+                    android.R.drawable.stat_sys_data_bluetooth,
                     context.getString(R.string.bluetooth_connection_permission_request),
                     System.currentTimeMillis());
-                String deviceName = device != null ? device.getAliasName() : null;
+                String deviceName = mDevice != null ? mDevice.getAliasName() : null;
                 notification.setLatestEventInfo(context,
                     context.getString(R.string.bluetooth_connection_permission_request),
                     context.getString(R.string.bluetooth_connection_notif_message, deviceName),
@@ -103,5 +118,68 @@
                 .getSystemService(Context.NOTIFICATION_SERVICE);
             manager.cancel(NOTIFICATION_ID);
         }
-   }
+    }
+
+    /**
+     * @return true user had made a choice, this method replies to the request according
+     *              to user's previous decision
+     *         false user hadnot made any choice on this device
+     */
+    private boolean checkUserChoice() {
+        boolean processed = false;
+
+        // we only remember PHONEBOOK permission
+        if (mRequestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
+            return processed;
+        }
+
+        LocalBluetoothManager bluetoothManager = LocalBluetoothManager.getInstance(mContext);
+        CachedBluetoothDeviceManager cachedDeviceManager =
+            bluetoothManager.getCachedDeviceManager();
+        CachedBluetoothDevice cachedDevice = cachedDeviceManager.findDevice(mDevice);
+
+        if (cachedDevice == null) {
+            cachedDevice = cachedDeviceManager.addDevice(bluetoothManager.getBluetoothAdapter(),
+                bluetoothManager.getProfileManager(), mDevice);
+        }
+
+        int phonebookPermission = cachedDevice.getPhonebookPermissionChoice();
+
+        if (phonebookPermission == CachedBluetoothDevice.PHONEBOOK_ACCESS_UNKNOWN) {
+            return processed;
+        }
+
+        String intentName = BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY;
+        if (phonebookPermission == CachedBluetoothDevice.PHONEBOOK_ACCESS_ALLOWED) {
+            sendIntentToReceiver(intentName, true, BluetoothDevice.EXTRA_ALWAYS_ALLOWED, true);
+            processed = true;
+        } else if (phonebookPermission == CachedBluetoothDevice.PHONEBOOK_ACCESS_REJECTED) {
+            sendIntentToReceiver(intentName, false,
+                                 null, false // dummy value, no effect since previous param is null
+                                 );
+            processed = true;
+        } else {
+            Log.e(TAG, "Bad phonebookPermission: " + phonebookPermission);
+        }
+        return processed;
+    }
+
+    private void sendIntentToReceiver(final String intentName, final boolean allowed,
+                                      final String extraName, final boolean extraValue) {
+        Intent intent = new Intent(intentName);
+
+        if (mReturnPackage != null && mReturnClass != null) {
+            intent.setClassName(mReturnPackage, mReturnClass);
+        }
+
+        intent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
+                        allowed ? BluetoothDevice.CONNECTION_ACCESS_YES :
+                        BluetoothDevice.CONNECTION_ACCESS_NO);
+
+        if (extraName != null) {
+            intent.putExtra(extraName, extraValue);
+        }
+        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
+        mContext.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH_ADMIN);
+    }
 }
diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
index 8082314..01fd1b2 100644
--- a/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
+++ b/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
@@ -19,6 +19,8 @@
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.SharedPreferences;
 import android.os.ParcelUuid;
 import android.os.SystemClock;
 import android.text.TextUtils;
@@ -40,6 +42,7 @@
     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;
@@ -60,8 +63,20 @@
 
     private boolean mVisible;
 
+    private int mPhonebookPermissionChoice;
+
     private final Collection<Callback> mCallbacks = new ArrayList<Callback>();
 
+    // Following constants indicate the user's choices of Phone book access settings
+    // User hasn't made any choice or settings app has wiped out the memory
+    final static int PHONEBOOK_ACCESS_UNKNOWN = 0;
+    // User has accepted the connection and let Settings app remember the decision
+    final static int PHONEBOOK_ACCESS_ALLOWED = 1;
+    // User has rejected the connection and let Settings app remember the decision
+    final static int PHONEBOOK_ACCESS_REJECTED = 2;
+
+    private final static String PHONEBOOK_PREFS_NAME = "bluetooth_phonebook_permission";
+
     /**
      * When we connect to multiple profiles, we only want to display a single
      * error even if they all fail. This tracks that state.
@@ -125,9 +140,11 @@
         }
     }
 
-    CachedBluetoothDevice(LocalBluetoothAdapter adapter,
-            LocalBluetoothProfileManager profileManager,
-            BluetoothDevice device) {
+    CachedBluetoothDevice(Context context,
+                          LocalBluetoothAdapter adapter,
+                          LocalBluetoothProfileManager profileManager,
+                          BluetoothDevice device) {
+        mContext = context;
         mLocalAdapter = adapter;
         mProfileManager = profileManager;
         mDevice = device;
@@ -305,9 +322,9 @@
         fetchName();
         fetchBtClass();
         updateProfiles();
+        fetchPhonebookPermissionChoice();
 
         mVisible = false;
-
         dispatchAttributesChanged();
     }
 
@@ -470,6 +487,7 @@
         if (bondState == BluetoothDevice.BOND_NONE) {
             mProfiles.clear();
             mConnectAfterPairing = false;  // cancel auto-connect
+            setPhonebookPermissionChoice(PHONEBOOK_ACCESS_UNKNOWN);
         }
 
         refresh();
@@ -580,4 +598,28 @@
     public interface Callback {
         void onDeviceAttributesChanged();
     }
+
+    int getPhonebookPermissionChoice() {
+        return mPhonebookPermissionChoice;
+    }
+
+    void setPhonebookPermissionChoice(int permissionChoice) {
+        SharedPreferences.Editor editor =
+            mContext.getSharedPreferences(PHONEBOOK_PREFS_NAME, Context.MODE_PRIVATE).edit();
+        if (permissionChoice == PHONEBOOK_ACCESS_UNKNOWN) {
+            editor.remove(mDevice.getAddress());
+        } else {
+            editor.putInt(mDevice.getAddress(), permissionChoice);
+        }
+        editor.commit();
+        mPhonebookPermissionChoice = permissionChoice;
+    }
+
+    private void fetchPhonebookPermissionChoice() {
+        SharedPreferences preference = mContext.getSharedPreferences(PHONEBOOK_PREFS_NAME,
+                                                                     Context.MODE_PRIVATE);
+        mPhonebookPermissionChoice = preference.getInt(mDevice.getAddress(),
+                                                       PHONEBOOK_ACCESS_UNKNOWN);
+    }
+
 }
diff --git a/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java b/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java
index b511cb3..77f6b2c 100644
--- a/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java
+++ b/src/com/android/settings/bluetooth/CachedBluetoothDeviceManager.java
@@ -17,6 +17,8 @@
 package com.android.settings.bluetooth;
 
 import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -26,10 +28,17 @@
  * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
  */
 final class CachedBluetoothDeviceManager {
+    private static final String TAG = "CachedBluetoothDeviceManager";
+    private static final boolean DEBUG = Utils.D;
 
+    private Context mContext;
     private final List<CachedBluetoothDevice> mCachedDevices =
             new ArrayList<CachedBluetoothDevice>();
 
+    CachedBluetoothDeviceManager(Context context) {
+        mContext = context;
+    }
+
     public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
         return new ArrayList<CachedBluetoothDevice>(mCachedDevices);
     }
@@ -74,8 +83,8 @@
     CachedBluetoothDevice addDevice(LocalBluetoothAdapter adapter,
             LocalBluetoothProfileManager profileManager,
             BluetoothDevice device) {
-        CachedBluetoothDevice newDevice = new CachedBluetoothDevice(adapter, profileManager,
-                device);
+        CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, adapter,
+            profileManager, device);
         mCachedDevices.add(newDevice);
         return newDevice;
     }
@@ -124,4 +133,10 @@
             cachedDevice.onUuidChanged();
         }
     }
+
+    private void log(String msg) {
+        if (DEBUG) {
+            Log.d(TAG, msg);
+        }
+    }
 }
diff --git a/src/com/android/settings/bluetooth/LocalBluetoothManager.java b/src/com/android/settings/bluetooth/LocalBluetoothManager.java
index 3357e59..ae8dec2 100644
--- a/src/com/android/settings/bluetooth/LocalBluetoothManager.java
+++ b/src/com/android/settings/bluetooth/LocalBluetoothManager.java
@@ -74,7 +74,7 @@
         mContext = context;
         mLocalAdapter = adapter;
 
-        mCachedDeviceManager = new CachedBluetoothDeviceManager();
+        mCachedDeviceManager = new CachedBluetoothDeviceManager(context);
         mEventManager = new BluetoothEventManager(mLocalAdapter,
                 mCachedDeviceManager, context);
         mProfileManager = new LocalBluetoothProfileManager(context,