• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 package com.android.bluetooth.pbapclient;
17 
18 import android.accounts.Account;
19 import android.accounts.AccountManager;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothSocket;
23 import android.bluetooth.BluetoothUuid;
24 import android.bluetooth.SdpPseRecord;
25 import android.content.Context;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.os.Message;
29 import android.provider.CallLog;
30 import android.util.Log;
31 
32 import com.android.bluetooth.BluetoothObexTransport;
33 import com.android.bluetooth.R;
34 
35 import java.io.IOException;
36 
37 import javax.obex.ClientSession;
38 import javax.obex.HeaderSet;
39 import javax.obex.ResponseCodes;
40 
41 /* Bluetooth/pbapclient/PbapClientConnectionHandler is responsible
42  * for connecting, disconnecting and downloading contacts from the
43  * PBAP PSE when commanded. It receives all direction from the
44  * controlling state machine.
45  */
46 class PbapClientConnectionHandler extends Handler {
47     static final String TAG = "PBAP PCE handler";
48     static final boolean DBG = true;
49     static final int MSG_CONNECT = 1;
50     static final int MSG_DISCONNECT = 2;
51     static final int MSG_DOWNLOAD = 3;
52 
53     // The following constants are pulled from the Bluetooth Phone Book Access Profile specification
54     // 1.1
55     private static final byte[] PBAP_TARGET = new byte[]{
56             0x79, 0x61, 0x35, (byte) 0xf0, (byte) 0xf0, (byte) 0xc5, 0x11, (byte) 0xd8, 0x09, 0x66,
57             0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
58     };
59 
60     private static final int PBAP_FEATURE_DEFAULT_IMAGE_FORMAT = 0x00000200;
61     private static final int PBAP_FEATURE_BROWSING = 0x00000002;
62     private static final int PBAP_FEATURE_DOWNLOADING = 0x00000001;
63 
64     private static final long PBAP_FILTER_VERSION = 1 << 0;
65     private static final long PBAP_FILTER_FN = 1 << 1;
66     private static final long PBAP_FILTER_N = 1 << 2;
67     private static final long PBAP_FILTER_PHOTO = 1 << 3;
68     private static final long PBAP_FILTER_ADR = 1 << 5;
69     private static final long PBAP_FILTER_TEL = 1 << 7;
70     private static final long PBAP_FILTER_EMAIL = 1 << 8;
71     private static final long PBAP_FILTER_NICKNAME = 1 << 23;
72 
73     private static final int PBAP_SUPPORTED_FEATURE =
74             PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_BROWSING | PBAP_FEATURE_DOWNLOADING;
75     private static final long PBAP_REQUESTED_FIELDS = PBAP_FILTER_VERSION | PBAP_FILTER_FN
76             | PBAP_FILTER_N | PBAP_FILTER_PHOTO | PBAP_FILTER_ADR | PBAP_FILTER_TEL
77             | PBAP_FILTER_NICKNAME;
78     private static final int PBAP_V1_2 = 0x0102;
79     private static final int L2CAP_INVALID_PSM = -1;
80 
81     public static final String PB_PATH = "telecom/pb.vcf";
82     public static final String MCH_PATH = "telecom/mch.vcf";
83     public static final String ICH_PATH = "telecom/ich.vcf";
84     public static final String OCH_PATH = "telecom/och.vcf";
85     public static final byte VCARD_TYPE_21 = 0;
86     public static final byte VCARD_TYPE_30 = 1;
87 
88     private Account mAccount;
89     private AccountManager mAccountManager;
90     private BluetoothSocket mSocket;
91     private final BluetoothAdapter mAdapter;
92     private final BluetoothDevice mDevice;
93     // PSE SDP Record for current device.
94     private SdpPseRecord mPseRec = null;
95     private ClientSession mObexSession;
96     private Context mContext;
97     private BluetoothPbapObexAuthenticator mAuth = null;
98     private final PbapClientStateMachine mPbapClientStateMachine;
99     private boolean mAccountCreated;
100 
PbapClientConnectionHandler(Looper looper, Context context, PbapClientStateMachine stateMachine, BluetoothDevice device)101     PbapClientConnectionHandler(Looper looper, Context context, PbapClientStateMachine stateMachine,
102             BluetoothDevice device) {
103         super(looper);
104         mAdapter = BluetoothAdapter.getDefaultAdapter();
105         mDevice = device;
106         mContext = context;
107         mPbapClientStateMachine = stateMachine;
108         mAuth = new BluetoothPbapObexAuthenticator(this);
109         mAccountManager = AccountManager.get(mPbapClientStateMachine.getContext());
110         mAccount = new Account(mDevice.getAddress(), mContext.getString(
111                 R.string.pbap_account_type));
112     }
113 
114     /**
115      * Constructs PCEConnectionHandler object
116      *
117      * @param Builder To build  BluetoothPbapClientHandler Instance.
118      */
PbapClientConnectionHandler(Builder pceHandlerbuild)119     PbapClientConnectionHandler(Builder pceHandlerbuild) {
120         super(pceHandlerbuild.looper);
121         mAdapter = BluetoothAdapter.getDefaultAdapter();
122         mDevice = pceHandlerbuild.device;
123         mContext = pceHandlerbuild.context;
124         mPbapClientStateMachine = pceHandlerbuild.clientStateMachine;
125         mAuth = new BluetoothPbapObexAuthenticator(this);
126         mAccountManager = AccountManager.get(mPbapClientStateMachine.getContext());
127         mAccount = new Account(mDevice.getAddress(), mContext.getString(
128                 R.string.pbap_account_type));
129     }
130 
131     public static class Builder {
132 
133         private Looper looper;
134         private Context context;
135         private BluetoothDevice device;
136         private PbapClientStateMachine clientStateMachine;
137 
setLooper(Looper loop)138         public Builder setLooper(Looper loop) {
139             this.looper = loop;
140             return this;
141         }
142 
setClientSM(PbapClientStateMachine clientStateMachine)143         public Builder setClientSM(PbapClientStateMachine clientStateMachine) {
144             this.clientStateMachine = clientStateMachine;
145             return this;
146         }
147 
setRemoteDevice(BluetoothDevice device)148         public Builder setRemoteDevice(BluetoothDevice device) {
149             this.device = device;
150             return this;
151         }
152 
setContext(Context context)153         public Builder setContext(Context context) {
154             this.context = context;
155             return this;
156         }
157 
build()158         public PbapClientConnectionHandler build() {
159             PbapClientConnectionHandler pbapClientHandler = new PbapClientConnectionHandler(this);
160             return pbapClientHandler;
161         }
162 
163     }
164 
165     @Override
handleMessage(Message msg)166     public void handleMessage(Message msg) {
167         if (DBG) Log.d(TAG, "Handling Message = " + msg.what);
168         switch (msg.what) {
169             case MSG_CONNECT:
170                 mPseRec = (SdpPseRecord) msg.obj;
171                 /* To establish a connection, first open a socket and then create an OBEX session */
172                 if (connectSocket()) {
173                     if (DBG) Log.d(TAG, "Socket connected");
174                 } else {
175                     Log.w(TAG, "Socket CONNECT Failure ");
176                     mPbapClientStateMachine.obtainMessage(
177                             PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
178                     return;
179                 }
180 
181                 if (connectObexSession()) {
182                     mPbapClientStateMachine.obtainMessage(
183                             PbapClientStateMachine.MSG_CONNECTION_COMPLETE).sendToTarget();
184                 } else {
185                     mPbapClientStateMachine.obtainMessage(
186                             PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
187                 }
188                 break;
189 
190             case MSG_DISCONNECT:
191                 if (DBG) Log.d(TAG, "Starting Disconnect");
192                 try {
193                     if (mObexSession != null) {
194                         if (DBG) Log.d(TAG, "obexSessionDisconnect" + mObexSession);
195                         mObexSession.disconnect(null);
196                         mObexSession.close();
197                     }
198 
199                     if (DBG) Log.d(TAG, "Closing Socket");
200                     closeSocket();
201                 } catch (IOException e) {
202                     Log.w(TAG, "DISCONNECT Failure ", e);
203                 }
204                 if (DBG) Log.d(TAG, "Completing Disconnect");
205                 removeAccount(mAccount);
206                 mContext.getContentResolver()
207                         .delete(CallLog.Calls.CONTENT_URI, null, null);
208                 mPbapClientStateMachine.obtainMessage(
209                         PbapClientStateMachine.MSG_CONNECTION_CLOSED).sendToTarget();
210                 break;
211 
212             case MSG_DOWNLOAD:
213                 try {
214                     mAccountCreated = addAccount(mAccount);
215                     if (mAccountCreated == false) {
216                         Log.e(TAG, "Account creation failed.");
217                         return;
218                     }
219                     // Start at contact 1 to exclued Owner Card PBAP 1.1 sec 3.1.5.2
220                     BluetoothPbapRequestPullPhoneBook request =
221                             new BluetoothPbapRequestPullPhoneBook(
222                                     PB_PATH, mAccount, PBAP_REQUESTED_FIELDS, VCARD_TYPE_30, 0, 1);
223                     request.execute(mObexSession);
224                     PhonebookPullRequest processor =
225                             new PhonebookPullRequest(mPbapClientStateMachine.getContext(),
226                                     mAccount);
227                     processor.setResults(request.getList());
228                     processor.onPullComplete();
229 
230                     downloadCallLog(MCH_PATH);
231                     downloadCallLog(ICH_PATH);
232                     downloadCallLog(OCH_PATH);
233                 } catch (IOException e) {
234                     Log.w(TAG, "DOWNLOAD_CONTACTS Failure" + e.toString());
235                 }
236                 break;
237 
238             default:
239                 Log.w(TAG, "Received Unexpected Message");
240         }
241         return;
242     }
243 
244     /* Utilize SDP, if available, to create a socket connection over L2CAP, RFCOMM specified
245      * channel, or RFCOMM default channel. */
connectSocket()246     private boolean connectSocket() {
247         try {
248             /* Use BluetoothSocket to connect */
249             if (mPseRec == null) {
250                 // BackWardCompatability: Fall back to create RFCOMM through UUID.
251                 Log.v(TAG, "connectSocket: UUID: " + BluetoothUuid.PBAP_PSE.getUuid());
252                 mSocket = mDevice.createRfcommSocketToServiceRecord(
253                         BluetoothUuid.PBAP_PSE.getUuid());
254             } else if (mPseRec.getL2capPsm() != L2CAP_INVALID_PSM) {
255                 Log.v(TAG, "connectSocket: PSM: " + mPseRec.getL2capPsm());
256                 mSocket = mDevice.createL2capSocket(mPseRec.getL2capPsm());
257             } else {
258                 Log.v(TAG, "connectSocket: channel: " + mPseRec.getRfcommChannelNumber());
259                 mSocket = mDevice.createRfcommSocket(mPseRec.getRfcommChannelNumber());
260             }
261 
262             if (mSocket != null) {
263                 mSocket.connect();
264                 return true;
265             } else {
266                 Log.w(TAG, "Could not create socket");
267             }
268         } catch (IOException e) {
269             Log.e(TAG, "Error while connecting socket", e);
270         }
271         return false;
272     }
273 
274     /* Connect an OBEX session over the already connected socket.  First establish an OBEX Transport
275      * abstraction, then establish a Bluetooth Authenticator, and finally issue the connect call */
connectObexSession()276     private boolean connectObexSession() {
277         boolean connectionSuccessful = false;
278 
279         try {
280             if (DBG) Log.v(TAG, "Start Obex Client Session");
281             BluetoothObexTransport transport = new BluetoothObexTransport(mSocket);
282             mObexSession = new ClientSession(transport);
283             mObexSession.setAuthenticator(mAuth);
284 
285             HeaderSet connectionRequest = new HeaderSet();
286             connectionRequest.setHeader(HeaderSet.TARGET, PBAP_TARGET);
287 
288             if (mPseRec != null) {
289                 if (DBG) {
290                     Log.d(TAG, "Remote PbapSupportedFeatures "
291                             + mPseRec.getSupportedFeatures());
292                 }
293 
294                 ObexAppParameters oap = new ObexAppParameters();
295 
296                 if (mPseRec.getProfileVersion() >= PBAP_V1_2) {
297                     oap.add(BluetoothPbapRequest.OAP_TAGID_PBAP_SUPPORTED_FEATURES,
298                             PBAP_SUPPORTED_FEATURE);
299                 }
300 
301                 oap.addToHeaderSet(connectionRequest);
302             }
303             HeaderSet connectionResponse = mObexSession.connect(connectionRequest);
304 
305             connectionSuccessful = (connectionResponse.getResponseCode() ==
306                     ResponseCodes.OBEX_HTTP_OK);
307             if (DBG) Log.d(TAG, "Success = " + Boolean.toString(connectionSuccessful));
308         } catch (IOException e) {
309             Log.w(TAG, "CONNECT Failure " + e.toString());
310             closeSocket();
311         }
312         return connectionSuccessful;
313     }
314 
abort()315     public void abort() {
316         // Perform forced cleanup, it is ok if the handler throws an exception this will free the
317         // handler to complete what it is doing and finish with cleanup.
318         closeSocket();
319         this.getLooper().getThread().interrupt();
320     }
321 
closeSocket()322     private void closeSocket() {
323         try {
324             if (mSocket != null) {
325                 if (DBG) Log.d(TAG, "Closing socket" + mSocket);
326                 mSocket.close();
327                 mSocket = null;
328             }
329         } catch (IOException e) {
330             Log.e(TAG, "Error when closing socket", e);
331             mSocket = null;
332         }
333     }
334 
downloadCallLog(String path)335     void downloadCallLog(String path) {
336         try {
337             BluetoothPbapRequestPullPhoneBook request =
338                     new BluetoothPbapRequestPullPhoneBook(path, mAccount, 0, VCARD_TYPE_30, 0, 0);
339             request.execute(mObexSession);
340             CallLogPullRequest processor =
341                     new CallLogPullRequest(mPbapClientStateMachine.getContext(), path);
342             processor.setResults(request.getList());
343             processor.onPullComplete();
344         } catch (IOException e) {
345             Log.w(TAG, "Download call log failure");
346         }
347     }
348 
addAccount(Account account)349     private boolean addAccount(Account account) {
350         if (mAccountManager.addAccountExplicitly(account, null, null)) {
351             if (DBG) {
352                 Log.d(TAG, "Added account " + mAccount);
353             }
354             return true;
355         }
356         return false;
357     }
358 
removeAccount(Account acc)359     private void removeAccount(Account acc) {
360         if (mAccountManager.removeAccountExplicitly(acc)) {
361             if (DBG) {
362                 Log.d(TAG, "Removed account " + acc);
363             }
364         } else {
365             Log.e(TAG, "Failed to remove account " + mAccount);
366         }
367     }
368 }
369