BluetoothManager.java revision 593ab38970a84a60ac39edba4306647c8b66436d
127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon/*
227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon * Copyright (C) 2013 The Android Open Source Project
327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon *
427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon * Licensed under the Apache License, Version 2.0 (the "License");
527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon * you may not use this file except in compliance with the License.
627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon * You may obtain a copy of the License at
727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon *
827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon *      http://www.apache.org/licenses/LICENSE-2.0
927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon *
1027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon * Unless required by applicable law or agreed to in writing, software
1127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon * distributed under the License is distributed on an "AS IS" BASIS,
1227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon * See the License for the specific language governing permissions and
1427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon * limitations under the License.
1527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon */
1627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
1727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonpackage com.android.phone;
1827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
1927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport com.google.android.collect.Lists;
2027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport com.google.common.base.Preconditions;
2127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
2227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport android.bluetooth.BluetoothAdapter;
2327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport android.bluetooth.BluetoothDevice;
2427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport android.bluetooth.BluetoothHeadset;
2527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport android.bluetooth.BluetoothProfile;
2627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport android.content.BroadcastReceiver;
2727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport android.content.Context;
2827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport android.content.Intent;
2927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport android.content.IntentFilter;
3027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport android.os.Handler;
3127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport android.os.Message;
3227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport android.os.SystemClock;
3327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport android.os.SystemProperties;
3427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport android.util.Log;
3527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
3627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport com.android.internal.telephony.CallManager;
3727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
3827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonimport java.util.List;
3927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
4027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon/**
41593ab38970a84a60ac39edba4306647c8b66436dSantos Cordon * Listens to and caches bluetooth headset state.  Used By the AudioRouter for maintaining
42593ab38970a84a60ac39edba4306647c8b66436dSantos Cordon * overall audio state for use in the UI layer. Also provides method for connecting the bluetooth
43593ab38970a84a60ac39edba4306647c8b66436dSantos Cordon * headset to the phone call.
4427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon */
4527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordonpublic class BluetoothManager {
46593ab38970a84a60ac39edba4306647c8b66436dSantos Cordon    private static final String LOG_TAG = BluetoothManager.class.getSimpleName();
4727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private static final boolean DBG =
4827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
4927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
5027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
5127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private final BluetoothAdapter mBluetoothAdapter;
5227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private final CallManager mCallManager;
5327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private final Context mContext;
5427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
5527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private BluetoothHeadset mBluetoothHeadset;
5627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private int mBluetoothHeadsetState = BluetoothProfile.STATE_DISCONNECTED;
5727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private int mBluetoothHeadsetAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
5827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private boolean mShowBluetoothIndication = false;
5927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private boolean mBluetoothConnectionPending = false;
6027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private long mBluetoothConnectionRequestTime;
6127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
6227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    // Broadcast receiver for various intent broadcasts (see onCreate())
6327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private final BroadcastReceiver mReceiver = new BluetoothBroadcastReceiver();
6427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
6527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private final List<BluetoothIndicatorListener> mListeners = Lists.newArrayList();
6627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
6727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    public BluetoothManager(Context context, CallManager callManager) {
6827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        mContext = context;
6927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        mCallManager = callManager;
7027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
7127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
7227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        init(mContext);
7327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // TODO(klp): Listen for changes to the call list/state.
7427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
7527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
7627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /* package */ boolean isBluetoothHeadsetAudioOn() {
7727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        return (mBluetoothHeadsetAudioState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
7827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
7927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
8027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    //
8127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    // Bluetooth helper methods.
8227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    //
8327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    // - BluetoothAdapter is the Bluetooth system service.  If
8427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    //   getDefaultAdapter() returns null
8527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    //   then the device is not BT capable.  Use BluetoothDevice.isEnabled()
8627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    //   to see if BT is enabled on the device.
8727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    //
8827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    // - BluetoothHeadset is the API for the control connection to a
8927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    //   Bluetooth Headset.  This lets you completely connect/disconnect a
9027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    //   headset (which we don't do from the Phone UI!) but also lets you
9127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    //   get the address of the currently active headset and see whether
9227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    //   it's currently connected.
9327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
9427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /**
9527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * @return true if the Bluetooth on/off switch in the UI should be
9627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     *         available to the user (i.e. if the device is BT-capable
9727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     *         and a headset is connected.)
9827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     */
9927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /* package */ boolean isBluetoothAvailable() {
10027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (VDBG) log("isBluetoothAvailable()...");
10127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
10227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // There's no need to ask the Bluetooth system service if BT is enabled:
10327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        //
10427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        //    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
10527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        //    if ((adapter == null) || !adapter.isEnabled()) {
10627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        //        if (DBG) log("  ==> FALSE (BT not enabled)");
10727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        //        return false;
10827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        //    }
10927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        //    if (DBG) log("  - BT enabled!  device name " + adapter.getName()
11027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        //                 + ", address " + adapter.getAddress());
11127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        //
11227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // ...since we already have a BluetoothHeadset instance.  We can just
11327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // call isConnected() on that, and assume it'll be false if BT isn't
11427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // enabled at all.
11527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
11627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // Check if there's a connected headset, using the BluetoothHeadset API.
11727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        boolean isConnected = false;
11827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (mBluetoothHeadset != null) {
11927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
12027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
12127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            if (deviceList.size() > 0) {
12227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                BluetoothDevice device = deviceList.get(0);
12327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                isConnected = true;
12427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
12527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                if (VDBG) log("  - headset state = " +
12627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                              mBluetoothHeadset.getConnectionState(device));
12727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                if (VDBG) log("  - headset address: " + device);
12827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                if (VDBG) log("  - isConnected: " + isConnected);
12927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            }
13027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        }
13127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
13227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (VDBG) log("  ==> " + isConnected);
13327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        return isConnected;
13427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
13527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
13627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /**
13727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * @return true if a BT Headset is available, and its audio is currently connected.
13827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     */
13927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /* package */ boolean isBluetoothAudioConnected() {
14027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (mBluetoothHeadset == null) {
14127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            if (VDBG) log("isBluetoothAudioConnected: ==> FALSE (null mBluetoothHeadset)");
14227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            return false;
14327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        }
14427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
14527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
14627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (deviceList.isEmpty()) {
14727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            return false;
14827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        }
14927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        BluetoothDevice device = deviceList.get(0);
15027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        boolean isAudioOn = mBluetoothHeadset.isAudioConnected(device);
15127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (VDBG) log("isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn);
15227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        return isAudioOn;
15327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
15427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
15527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /**
15627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * Helper method used to control the onscreen "Bluetooth" indication;
15727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * see InCallControlState.bluetoothIndicatorOn.
15827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     *
15927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * @return true if a BT device is available and its audio is currently connected,
16027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     *              <b>or</b> if we issued a BluetoothHeadset.connectAudio()
16127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     *              call within the last 5 seconds (which presumably means
16227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     *              that the BT audio connection is currently being set
16327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     *              up, and will be connected soon.)
16427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     */
16527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /* package */ boolean isBluetoothAudioConnectedOrPending() {
16627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (isBluetoothAudioConnected()) {
16727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> TRUE (really connected)");
16827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            return true;
16927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        }
17027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
17127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // If we issued a connectAudio() call "recently enough", even
17227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // if BT isn't actually connected yet, let's still pretend BT is
17327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // on.  This makes the onscreen indication more responsive.
17427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (mBluetoothConnectionPending) {
17527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            long timeSinceRequest =
17627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                    SystemClock.elapsedRealtime() - mBluetoothConnectionRequestTime;
17727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            if (timeSinceRequest < 5000 /* 5 seconds */) {
17827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> TRUE (requested "
17927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                             + timeSinceRequest + " msec ago)");
18027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                return true;
18127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            } else {
18227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> FALSE (request too old: "
18327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                             + timeSinceRequest + " msec ago)");
18427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                mBluetoothConnectionPending = false;
18527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                return false;
18627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            }
18727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        }
18827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
18927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> FALSE");
19027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        return false;
19127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
19227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
19327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /**
19427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * @return true if the onscreen UI should currently be showing the
19527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * special "bluetooth is active" indication in a couple of places (in
19627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * which UI elements turn blue and/or show the bluetooth logo.)
19727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     *
19827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * This depends on the BluetoothHeadset state *and* the current
19927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * telephony state; see shouldShowBluetoothIndication().
20027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     *
20127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * @see CallCard
20227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * @see NotificationMgr.updateInCallNotification
20327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     */
20427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /* package */ boolean showBluetoothIndication() {
20527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        return mShowBluetoothIndication;
20627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
20727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
20827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /**
20927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * Recomputes the mShowBluetoothIndication flag based on the current
21027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * bluetooth state and current telephony state.
21127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     *
21227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * This needs to be called any time the bluetooth headset state or the
21327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * telephony state changes.
21427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     */
21527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /* package */ void updateBluetoothIndication() {
21627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        mShowBluetoothIndication = shouldShowBluetoothIndication(mBluetoothHeadsetState,
21727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                                                                 mBluetoothHeadsetAudioState,
21827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                                                                 mCallManager);
21927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
22027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        notifyListeners(mShowBluetoothIndication);
22127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
22227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
2239b7bac7705b3afcae7b010edc2032c7c0f37f770Santos Cordon    public void addBluetoothIndicatorListener(BluetoothIndicatorListener listener) {
22427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (!mListeners.contains(listener)) {
22527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            mListeners.add(listener);
22627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        }
22727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
22827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
2299b7bac7705b3afcae7b010edc2032c7c0f37f770Santos Cordon    public void removeBluetoothIndicatorListener(BluetoothIndicatorListener listener) {
2309b7bac7705b3afcae7b010edc2032c7c0f37f770Santos Cordon        if (mListeners.contains(listener)) {
2319b7bac7705b3afcae7b010edc2032c7c0f37f770Santos Cordon            mListeners.remove(listener);
2329b7bac7705b3afcae7b010edc2032c7c0f37f770Santos Cordon        }
2339b7bac7705b3afcae7b010edc2032c7c0f37f770Santos Cordon    }
2349b7bac7705b3afcae7b010edc2032c7c0f37f770Santos Cordon
23527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private void notifyListeners(boolean showBluetoothOn) {
23627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        for (int i = 0; i < mListeners.size(); i++) {
23727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            mListeners.get(i).onBluetoothIndicationChange(showBluetoothOn, this);
23827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        }
23927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
24027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
24127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private void init(Context context) {
24227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        Preconditions.checkNotNull(context);
24327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
24427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (mBluetoothAdapter != null) {
24527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            mBluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
24627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                                    BluetoothProfile.HEADSET);
24727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        }
24827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
24927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // Register for misc other intent broadcasts.
25027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        IntentFilter intentFilter =
25127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
25227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
25327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        context.registerReceiver(mReceiver, intentFilter);
25427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
25527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
25627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private void tearDown() {
25727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (mBluetoothHeadset != null) {
25827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
25927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            mBluetoothHeadset = null;
26027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        }
26127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
26227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
26327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
26427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon             new BluetoothProfile.ServiceListener() {
26527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon         @Override
26627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon         public void onServiceConnected(int profile, BluetoothProfile proxy) {
26727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon             mBluetoothHeadset = (BluetoothHeadset) proxy;
26827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon             if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset);
26927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon         }
27027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
27127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon         @Override
27227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon         public void onServiceDisconnected(int profile) {
27327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon             mBluetoothHeadset = null;
27427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon         }
27527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    };
27627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
27727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /**
27827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * UI policy helper function for the couple of places in the UI that
27927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * have some way of indicating that "bluetooth is in use."
28027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     *
28127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * @return true if the onscreen UI should indicate that "bluetooth is in use",
28227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     *         based on the specified bluetooth headset state, and the
28327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     *         current state of the phone.
28427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * @see showBluetoothIndication()
28527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     */
28627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private static boolean shouldShowBluetoothIndication(int bluetoothState,
28727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                                                         int bluetoothAudioState,
28827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                                                         CallManager cm) {
28927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // We want the UI to indicate that "bluetooth is in use" in two
29027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // slightly different cases:
29127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        //
29227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // (a) The obvious case: if a bluetooth headset is currently in
29327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        //     use for an ongoing call.
29427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        //
29527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // (b) The not-so-obvious case: if an incoming call is ringing,
29627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        //     and we expect that audio *will* be routed to a bluetooth
29727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        //     headset once the call is answered.
29827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
29927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        switch (cm.getState()) {
30027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            case OFFHOOK:
30127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                // This covers normal active calls, and also the case if
30227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                // the foreground call is DIALING or ALERTING.  In this
30327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                // case, bluetooth is considered "active" if a headset
30427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                // is connected *and* audio is being routed to it.
30527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                return ((bluetoothState == BluetoothHeadset.STATE_CONNECTED)
30627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                        && (bluetoothAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED));
30727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
30827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            case RINGING:
30927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                // If an incoming call is ringing, we're *not* yet routing
31027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                // audio to the headset (since there's no in-call audio
31127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                // yet!)  In this case, if a bluetooth headset is
31227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                // connected at all, we assume that it'll become active
31327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                // once the user answers the phone.
31427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                return (bluetoothState == BluetoothHeadset.STATE_CONNECTED);
31527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
31627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            default:  // Presumably IDLE
31727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                return false;
31827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        }
31927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
32027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
32127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private void dumpBluetoothState() {
32227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        log("============== dumpBluetoothState() =============");
32327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        log("= isBluetoothAvailable: " + isBluetoothAvailable());
32427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        log("= isBluetoothAudioConnected: " + isBluetoothAudioConnected());
32527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        log("= isBluetoothAudioConnectedOrPending: " + isBluetoothAudioConnectedOrPending());
32627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        log("= PhoneApp.showBluetoothIndication: "
32727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            + showBluetoothIndication());
32827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        log("=");
32927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (mBluetoothAdapter != null) {
33027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            if (mBluetoothHeadset != null) {
33127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
33227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
33327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                if (deviceList.size() > 0) {
33427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                    BluetoothDevice device = deviceList.get(0);
33527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                    log("= BluetoothHeadset.getCurrentDevice: " + device);
33627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                    log("= BluetoothHeadset.State: "
33727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                        + mBluetoothHeadset.getConnectionState(device));
33827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                    log("= BluetoothHeadset audio connected: " +
33927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                        mBluetoothHeadset.isAudioConnected(device));
34027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                }
34127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            } else {
34227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                log("= mBluetoothHeadset is null");
34327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            }
34427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        } else {
34527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            log("= mBluetoothAdapter is null; device is not BT capable");
34627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        }
34727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
34827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
34927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /* package */ void connectBluetoothAudio() {
35027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (VDBG) log("connectBluetoothAudio()...");
35127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (mBluetoothHeadset != null) {
35227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            // TODO(BT) check return
35327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            mBluetoothHeadset.connectAudio();
35427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        }
35527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
35627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // Watch out: The bluetooth connection doesn't happen instantly;
35727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // the connectAudio() call returns instantly but does its real
35827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // work in another thread.  The mBluetoothConnectionPending flag
35927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // is just a little trickery to ensure that the onscreen UI updates
36027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        // instantly. (See isBluetoothAudioConnectedOrPending() above.)
36127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        mBluetoothConnectionPending = true;
36227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();
36327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
36427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
36527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /* package */ void disconnectBluetoothAudio() {
36627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (VDBG) log("disconnectBluetoothAudio()...");
36727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        if (mBluetoothHeadset != null) {
36827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            mBluetoothHeadset.disconnectAudio();
36927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        }
37027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        mBluetoothConnectionPending = false;
37127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
37227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
37327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /**
37427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     * Receiver for misc intent broadcasts the BluetoothManager cares about.
37527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon     */
37627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private class BluetoothBroadcastReceiver extends BroadcastReceiver {
37727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        @Override
37827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        public void onReceive(Context context, Intent intent) {
37927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            String action = intent.getAction();
38027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
38127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
38227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                mBluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
38327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                                                          BluetoothHeadset.STATE_DISCONNECTED);
38427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                if (VDBG) Log.d(LOG_TAG, "mReceiver: HEADSET_STATE_CHANGED_ACTION");
38527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                if (VDBG) Log.d(LOG_TAG, "==> new state: " + mBluetoothHeadsetState);
38627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                // Also update any visible UI if necessary
38727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                updateBluetoothIndication();
38827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
38927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                mBluetoothHeadsetAudioState =
39027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                        intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
39127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                                           BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
39227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                if (VDBG) Log.d(LOG_TAG, "mReceiver: HEADSET_AUDIO_STATE_CHANGED_ACTION");
39327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                if (VDBG) Log.d(LOG_TAG, "==> new state: " + mBluetoothHeadsetAudioState);
39427a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon                updateBluetoothIndication();
39527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon            }
39627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        }
39727a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
39827a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
39927a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    private void log(String msg) {
40027a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon        Log.d(LOG_TAG, msg);
40127a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
40227a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon
40327a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    /* package */ interface BluetoothIndicatorListener {
4049b7bac7705b3afcae7b010edc2032c7c0f37f770Santos Cordon        public void onBluetoothIndicationChange(boolean isConnected, BluetoothManager manager);
40527a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon    }
40627a3c1f96fab43970e56a5eaa39551a4a248994fSantos Cordon}
407