Move BluetoothManager to telecomm.

Copy over bluetooth manager from teleservice and add usage in
CallAudioManager (for audio routing), Ringer (ringtone routing),
InCallTonePlayer (tone routing).

Change-Id: I015961aebf42389a7f4cf3a5f89ec194d6ca64e2
Bug: 13242863
diff --git a/src/com/android/telecomm/BluetoothManager.java b/src/com/android/telecomm/BluetoothManager.java
new file mode 100644
index 0000000..efdb724
--- /dev/null
+++ b/src/com/android/telecomm/BluetoothManager.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2014 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.telecomm;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.SystemClock;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Listens to and caches bluetooth headset state.  Used By the CallAudioManager for maintaining
+ * overall audio state. Also provides method for connecting the bluetooth headset to the phone call.
+ */
+public class BluetoothManager {
+
+    private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+            new BluetoothProfile.ServiceListener() {
+                @Override
+                public void onServiceConnected(int profile, BluetoothProfile proxy) {
+                    mBluetoothHeadset = (BluetoothHeadset) proxy;
+                    Log.v(this, "- Got BluetoothHeadset: " + mBluetoothHeadset);
+                }
+
+                @Override
+                public void onServiceDisconnected(int profile) {
+                    mBluetoothHeadset = null;
+                }
+           };
+
+    /**
+     * Receiver for misc intent broadcasts the BluetoothManager cares about.
+     */
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+
+            if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
+                int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+                                                          BluetoothHeadset.STATE_DISCONNECTED);
+                Log.d(this, "mReceiver: HEADSET_STATE_CHANGED_ACTION");
+                Log.d(this, "==> new state: %s ", bluetoothHeadsetState);
+                updateBluetoothState();
+            } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+                int bluetoothHeadsetAudioState =
+                        intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+                                           BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+                Log.d(this, "mReceiver: HEADSET_AUDIO_STATE_CHANGED_ACTION");
+                Log.d(this, "==> new state: %s", bluetoothHeadsetAudioState);
+                updateBluetoothState();
+            }
+        }
+    };
+
+    private final BluetoothAdapter mBluetoothAdapter;
+    private final CallAudioManager mCallAudioManager;
+
+    private BluetoothHeadset mBluetoothHeadset;
+    private boolean mBluetoothConnectionPending = false;
+    private long mBluetoothConnectionRequestTime;
+
+
+    public BluetoothManager(Context context, CallAudioManager callAudioManager) {
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        mCallAudioManager = callAudioManager;
+
+        if (mBluetoothAdapter != null) {
+            mBluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
+                                    BluetoothProfile.HEADSET);
+        }
+
+        // Register for misc other intent broadcasts.
+        IntentFilter intentFilter =
+                new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+        intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
+        context.registerReceiver(mReceiver, intentFilter);
+    }
+
+    //
+    // Bluetooth helper methods.
+    //
+    // - BluetoothAdapter is the Bluetooth system service.  If
+    //   getDefaultAdapter() returns null
+    //   then the device is not BT capable.  Use BluetoothDevice.isEnabled()
+    //   to see if BT is enabled on the device.
+    //
+    // - BluetoothHeadset is the API for the control connection to a
+    //   Bluetooth Headset.  This lets you completely connect/disconnect a
+    //   headset (which we don't do from the Phone UI!) but also lets you
+    //   get the address of the currently active headset and see whether
+    //   it's currently connected.
+
+    /**
+     * @return true if the Bluetooth on/off switch in the UI should be
+     *         available to the user (i.e. if the device is BT-capable
+     *         and a headset is connected.)
+     */
+    boolean isBluetoothAvailable() {
+        Log.v(this, "isBluetoothAvailable()...");
+
+        // There's no need to ask the Bluetooth system service if BT is enabled:
+        //
+        //    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        //    if ((adapter == null) || !adapter.isEnabled()) {
+        //        Log.d(this, "  ==> FALSE (BT not enabled)");
+        //        return false;
+        //    }
+        //    Log.d(this, "  - BT enabled!  device name " + adapter.getName()
+        //                 + ", address " + adapter.getAddress());
+        //
+        // ...since we already have a BluetoothHeadset instance.  We can just
+        // call isConnected() on that, and assume it'll be false if BT isn't
+        // enabled at all.
+
+        // Check if there's a connected headset, using the BluetoothHeadset API.
+        boolean isConnected = false;
+        if (mBluetoothHeadset != null) {
+            List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
+
+            if (deviceList.size() > 0) {
+                BluetoothDevice device = deviceList.get(0);
+                isConnected = true;
+
+                Log.v(this, "  - headset state = " + mBluetoothHeadset.getConnectionState(device));
+                Log.v(this, "  - headset address: " + device);
+                Log.v(this, "  - isConnected: " + isConnected);
+            }
+        }
+
+        Log.v(this, "  ==> " + isConnected);
+        return isConnected;
+    }
+
+    /**
+     * @return true if a BT Headset is available, and its audio is currently connected.
+     */
+    boolean isBluetoothAudioConnected() {
+        if (mBluetoothHeadset == null) {
+            Log.v(this, "isBluetoothAudioConnected: ==> FALSE (null mBluetoothHeadset)");
+            return false;
+        }
+        List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
+
+        if (deviceList.isEmpty()) {
+            return false;
+        }
+        BluetoothDevice device = deviceList.get(0);
+        boolean isAudioOn = mBluetoothHeadset.isAudioConnected(device);
+        Log.v(this, "isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn);
+        return isAudioOn;
+    }
+
+    /**
+     * Helper method used to control the onscreen "Bluetooth" indication;
+     *
+     * @return true if a BT device is available and its audio is currently connected,
+     *              <b>or</b> if we issued a BluetoothHeadset.connectAudio()
+     *              call within the last 5 seconds (which presumably means
+     *              that the BT audio connection is currently being set
+     *              up, and will be connected soon.)
+     */
+    /* package */ boolean isBluetoothAudioConnectedOrPending() {
+        if (isBluetoothAudioConnected()) {
+            Log.v(this, "isBluetoothAudioConnectedOrPending: ==> TRUE (really connected)");
+            return true;
+        }
+
+        // If we issued a connectAudio() call "recently enough", even
+        // if BT isn't actually connected yet, let's still pretend BT is
+        // on.  This makes the onscreen indication more responsive.
+        if (mBluetoothConnectionPending) {
+            long timeSinceRequest =
+                    SystemClock.elapsedRealtime() - mBluetoothConnectionRequestTime;
+            if (timeSinceRequest < 5000 /* 5 seconds */) {
+                Log.v(this, "isBluetoothAudioConnectedOrPending: ==> TRUE (requested "
+                             + timeSinceRequest + " msec ago)");
+                return true;
+            } else {
+                Log.v(this, "isBluetoothAudioConnectedOrPending: ==> FALSE (request too old: "
+                             + timeSinceRequest + " msec ago)");
+                mBluetoothConnectionPending = false;
+                return false;
+            }
+        }
+
+        Log.v(this, "isBluetoothAudioConnectedOrPending: ==> FALSE");
+        return false;
+    }
+
+    /**
+     * Notified audio manager of a change to the bluetooth state.
+     */
+    void updateBluetoothState() {
+        mCallAudioManager.onBluetoothStateChange(this);
+    }
+
+    void connectBluetoothAudio() {
+        Log.v(this, "connectBluetoothAudio()...");
+        if (mBluetoothHeadset != null) {
+            mBluetoothHeadset.connectAudio();
+        }
+
+        // Watch out: The bluetooth connection doesn't happen instantly;
+        // the connectAudio() call returns instantly but does its real
+        // work in another thread.  The mBluetoothConnectionPending flag
+        // is just a little trickery to ensure that the onscreen UI updates
+        // instantly. (See isBluetoothAudioConnectedOrPending() above.)
+        mBluetoothConnectionPending = true;
+        mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();
+    }
+
+    void disconnectBluetoothAudio() {
+        Log.v(this, "disconnectBluetoothAudio()...");
+        if (mBluetoothHeadset != null) {
+            mBluetoothHeadset.disconnectAudio();
+        }
+        mBluetoothConnectionPending = false;
+    }
+
+    private void dumpBluetoothState() {
+        Log.d(this, "============== dumpBluetoothState() =============");
+        Log.d(this, "= isBluetoothAvailable: " + isBluetoothAvailable());
+        Log.d(this, "= isBluetoothAudioConnected: " + isBluetoothAudioConnected());
+        Log.d(this, "= isBluetoothAudioConnectedOrPending: " +
+                isBluetoothAudioConnectedOrPending());
+        Log.d(this, "=");
+        if (mBluetoothAdapter != null) {
+            if (mBluetoothHeadset != null) {
+                List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
+
+                if (deviceList.size() > 0) {
+                    BluetoothDevice device = deviceList.get(0);
+                    Log.d(this, "= BluetoothHeadset.getCurrentDevice: " + device);
+                    Log.d(this, "= BluetoothHeadset.State: "
+                        + mBluetoothHeadset.getConnectionState(device));
+                    Log.d(this, "= BluetoothHeadset audio connected: " +
+                        mBluetoothHeadset.isAudioConnected(device));
+                }
+            } else {
+                Log.d(this, "= mBluetoothHeadset is null");
+            }
+        } else {
+            Log.d(this, "= mBluetoothAdapter is null; device is not BT capable");
+        }
+    }
+}