PbapClientConnectionHandler.java revision 33e2ea9f0756c2dc4f8dccd712e38b860a694922
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 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
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     */
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
138        public Builder setLooper(Looper loop) {
139            this.looper = loop;
140            return this;
141        }
142
143        public Builder setClientSM(PbapClientStateMachine clientStateMachine) {
144            this.clientStateMachine = clientStateMachine;
145            return this;
146        }
147
148        public Builder setRemoteDevice(BluetoothDevice device) {
149            this.device = device;
150            return this;
151        }
152
153        public Builder setContext(Context context) {
154            this.context = context;
155            return this;
156        }
157
158        public PbapClientConnectionHandler build() {
159            PbapClientConnectionHandler pbapClientHandler = new PbapClientConnectionHandler(this);
160            return pbapClientHandler;
161        }
162
163    }
164
165    @Override
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. */
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 */
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
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
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
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
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
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