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