1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tv.settings.accessories; 18 19 import android.bluetooth.BluetoothA2dp; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothProfile; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.os.Handler; 28 import android.os.Message; 29 import android.util.Log; 30 31 public class BluetoothA2dpConnector implements BluetoothDevicePairer.BluetoothConnector { 32 33 public static final String TAG = "BluetoothA2dpConnector"; 34 35 private static final boolean DEBUG = false; 36 37 private static final int MSG_CONNECT_TIMEOUT = 1; 38 private static final int MSG_CONNECT = 2; 39 40 private static final int CONNECT_TIMEOUT_MS = 10000; 41 private static final int CONNECT_DELAY = 1000; 42 43 private Context mContext; 44 private BluetoothDevice mTarget; 45 private BluetoothDevicePairer.OpenConnectionCallback mOpenConnectionCallback; 46 private BluetoothA2dp mA2dpProfile; 47 private boolean mConnectionStateReceiverRegistered = false; 48 49 private Handler mHandler = new Handler() { 50 @Override 51 public void handleMessage(Message m) { 52 switch (m.what) { 53 case MSG_CONNECT_TIMEOUT: 54 failed(); 55 break; 56 case MSG_CONNECT: 57 if (mA2dpProfile == null) { 58 break; 59 } 60 mA2dpProfile.connect(mTarget); 61 // must set PRIORITY_AUTO_CONNECT or auto-connection will not 62 // occur, however this setting does not appear to be sticky 63 // across a reboot 64 mA2dpProfile.setPriority(mTarget, BluetoothProfile.PRIORITY_AUTO_CONNECT); 65 break; 66 default: 67 break; 68 } 69 } 70 }; 71 72 private BroadcastReceiver mConnectionStateReceiver = new BroadcastReceiver() { 73 @Override 74 public void onReceive(Context context, Intent intent) { 75 BluetoothDevice device = 76 (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 77 if (DEBUG) { 78 Log.d(TAG, "There was a connection status change for: " + device.getAddress()); 79 } 80 81 if (!device.equals(mTarget)) { 82 return; 83 } 84 85 if (BluetoothDevice.ACTION_UUID.equals(intent.getAction())) { 86 // regardless of the UUID content, at this point, we're sure we can initiate a 87 // profile connection. 88 mHandler.sendEmptyMessageDelayed(MSG_CONNECT_TIMEOUT, CONNECT_TIMEOUT_MS); 89 if (!mHandler.hasMessages(MSG_CONNECT)) { 90 mHandler.sendEmptyMessageDelayed(MSG_CONNECT, CONNECT_DELAY); 91 } 92 } else { // BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED 93 94 int previousState = intent.getIntExtra( 95 BluetoothA2dp.EXTRA_PREVIOUS_STATE, BluetoothA2dp.STATE_CONNECTING); 96 int state = intent.getIntExtra( 97 BluetoothA2dp.EXTRA_STATE, BluetoothA2dp.STATE_CONNECTING); 98 99 if (DEBUG) { 100 Log.d(TAG, "Connection states: old = " + previousState + ", new = " + state); 101 } 102 103 if (previousState == BluetoothA2dp.STATE_CONNECTING) { 104 if (state == BluetoothA2dp.STATE_CONNECTED) { 105 succeeded(); 106 } else if (state == BluetoothA2dp.STATE_DISCONNECTED) { 107 Log.d(TAG, "Failed to connect"); 108 failed(); 109 } 110 111 unregisterConnectionStateReceiver(); 112 closeA2dpProfileProxy(); 113 } 114 } 115 } 116 }; 117 succeeded()118 private void succeeded() { 119 mHandler.removeCallbacksAndMessages(null); 120 mOpenConnectionCallback.succeeded(); 121 } 122 failed()123 private void failed() { 124 mHandler.removeCallbacksAndMessages(null); 125 mOpenConnectionCallback.failed(); 126 } 127 128 private BluetoothProfile.ServiceListener mServiceConnection = 129 new BluetoothProfile.ServiceListener() { 130 131 @Override 132 public void onServiceDisconnected(int profile) { 133 Log.w(TAG, "Service disconnected, perhaps unexpectedly"); 134 unregisterConnectionStateReceiver(); 135 closeA2dpProfileProxy(); 136 failed(); 137 } 138 139 @Override 140 public void onServiceConnected(int profile, BluetoothProfile proxy) { 141 if (DEBUG) { 142 Log.d(TAG, "Connection made to bluetooth proxy." ); 143 } 144 mA2dpProfile = (BluetoothA2dp) proxy; 145 if (DEBUG) { 146 Log.d(TAG, "Connecting to target: " + mTarget.getAddress()); 147 } 148 149 registerConnectionStateReceiver(); 150 // We initiate SDP because connecting to A2DP before services are discovered leads to 151 // error. 152 mTarget.fetchUuidsWithSdp(); 153 } 154 }; 155 BluetoothA2dpConnector()156 private BluetoothA2dpConnector() { 157 } 158 BluetoothA2dpConnector(Context context, BluetoothDevice target, BluetoothDevicePairer.OpenConnectionCallback callback)159 public BluetoothA2dpConnector(Context context, BluetoothDevice target, 160 BluetoothDevicePairer.OpenConnectionCallback callback) { 161 mContext = context; 162 mTarget = target; 163 mOpenConnectionCallback = callback; 164 } 165 166 @Override openConnection(BluetoothAdapter adapter)167 public void openConnection(BluetoothAdapter adapter) { 168 if (DEBUG) { 169 Log.d(TAG, "opening connection"); 170 } 171 if (!adapter.getProfileProxy(mContext, mServiceConnection, BluetoothProfile.A2DP)) { 172 failed(); 173 } 174 } 175 closeA2dpProfileProxy()176 private void closeA2dpProfileProxy() { 177 mHandler.removeCallbacksAndMessages(null); 178 if (mA2dpProfile != null) { 179 try { 180 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 181 adapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dpProfile); 182 mA2dpProfile = null; 183 } catch (Throwable t) { 184 Log.w(TAG, "Error cleaning up A2DP proxy", t); 185 } 186 } 187 } 188 registerConnectionStateReceiver()189 private void registerConnectionStateReceiver() { 190 if (DEBUG) Log.d(TAG, "registerConnectionStateReceiver()"); 191 IntentFilter filter = new IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); 192 filter.addAction(BluetoothDevice.ACTION_UUID); 193 mContext.registerReceiver(mConnectionStateReceiver, filter); 194 mConnectionStateReceiverRegistered = true; 195 } 196 unregisterConnectionStateReceiver()197 private void unregisterConnectionStateReceiver() { 198 if (mConnectionStateReceiverRegistered) { 199 if (DEBUG) Log.d(TAG, "unregisterConnectionStateReceiver()"); 200 mContext.unregisterReceiver(mConnectionStateReceiver); 201 mConnectionStateReceiverRegistered = false; 202 } 203 } 204 205 } 206