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