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