PbapClientConnectionHandler.java revision 8b0649c5b76fb40fe0acb7772a71fa3ca727c34e
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.content.Context;
25import android.os.Handler;
26import android.os.HandlerThread;
27import android.os.Looper;
28import android.os.Message;
29import android.os.Process;
30import android.provider.CallLog;
31import android.util.Log;
32
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    public static final String PB_PATH = "telecom/pb.vcf";
61    public static final String MCH_PATH = "telecom/mch.vcf";
62    public static final String ICH_PATH = "telecom/ich.vcf";
63    public static final String OCH_PATH = "telecom/och.vcf";
64    public static final byte VCARD_TYPE_21 = 0;
65
66    private Account mAccount;
67    private AccountManager mAccountManager;
68    private BluetoothSocket mSocket;
69    private final BluetoothAdapter mAdapter;
70    private final BluetoothDevice mDevice;
71    private ClientSession mObexSession;
72    private Context mContext;
73    private BluetoothPbapObexAuthenticator mAuth = null;
74    private final PbapClientStateMachine mPbapClientStateMachine;
75    private boolean mAccountCreated;
76
77    PbapClientConnectionHandler(Looper looper, Context context, PbapClientStateMachine stateMachine,
78            BluetoothDevice device) {
79        super(looper);
80        mAdapter = BluetoothAdapter.getDefaultAdapter();
81        mDevice = device;
82        mContext = context;
83        mPbapClientStateMachine = stateMachine;
84        mAuth = new BluetoothPbapObexAuthenticator(this);
85        mAccountManager = AccountManager.get(mPbapClientStateMachine.getContext());
86        mAccount = new Account(mDevice.getAddress(), mContext.getString(
87                R.string.pbap_account_type));
88    }
89
90    @Override
91    public void handleMessage(Message msg) {
92        if (DBG) Log.d(TAG,"Handling Message = " +msg.what);
93        switch(msg.what) {
94            case MSG_CONNECT:
95                boolean connectionSuccessful = false;
96
97                try {
98                    /* To establish a connection first open a socket, establish a OBEX Transport
99                     * abstraction, establish a Bluetooth Authenticator, and finally attempt to
100                     * connect via an OBEX session */
101                    mSocket = mDevice.createRfcommSocketToServiceRecord(
102                            BluetoothUuid.PBAP_PSE.getUuid());
103                    mSocket.connect();
104
105                    BluetoothPbapObexTransport transport;
106                    transport = new BluetoothPbapObexTransport(mSocket);
107
108                    mObexSession  = new ClientSession(transport);
109                    mObexSession.setAuthenticator(mAuth);
110
111                    HeaderSet connectionRequest = new HeaderSet();
112                    connectionRequest.setHeader(HeaderSet.TARGET, PBAP_TARGET);
113                    HeaderSet connectionResponse = mObexSession.connect(connectionRequest);
114
115                    connectionSuccessful = (connectionResponse.getResponseCode() ==
116                            ResponseCodes.OBEX_HTTP_OK);
117                    if (DBG) Log.d(TAG,"Success = " + Boolean.toString(connectionSuccessful));
118                } catch (IOException e) {
119                    Log.w(TAG,"CONNECT Failure " + e.toString());
120                    closeSocket();
121                }
122
123                if (connectionSuccessful) {
124                    mPbapClientStateMachine.obtainMessage(
125                            PbapClientStateMachine.MSG_CONNECTION_COMPLETE).sendToTarget();
126                } else {
127                    mPbapClientStateMachine.obtainMessage(
128                            PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
129                }
130                break;
131
132            case MSG_DISCONNECT:
133                try {
134                    if (mObexSession != null) {
135                        mObexSession.disconnect(null);
136                    }
137                    closeSocket();
138                } catch (IOException e) {
139                    Log.w(TAG,"DISCONNECT Failure " + e.toString());
140                }
141                removeAccount(mAccount);
142                mContext.getContentResolver()
143                        .delete(CallLog.Calls.CONTENT_URI, null, null);
144                mPbapClientStateMachine.obtainMessage(
145                        PbapClientStateMachine.MSG_CONNECTION_CLOSED).sendToTarget();
146                break;
147
148            case MSG_DOWNLOAD:
149                try {
150                    mAccountCreated = addAccount(mAccount);
151                    if (mAccountCreated == false) {
152                        Log.e(TAG,"Account creation failed.");
153                        return;
154                    }
155                    BluetoothPbapRequestPullPhoneBook request =
156                            new BluetoothPbapRequestPullPhoneBook(PB_PATH, mAccount, 0,
157                            VCARD_TYPE_21, 0, 0);
158                    request.execute(mObexSession);
159                    PhonebookPullRequest processor =
160                        new PhonebookPullRequest(mPbapClientStateMachine.getContext(), mAccount);
161                    processor.setResults(request.getList());
162                    processor.onPullComplete();
163
164                    downloadCallLog(MCH_PATH);
165                    downloadCallLog(ICH_PATH);
166                    downloadCallLog(OCH_PATH);
167                } catch (IOException e) {
168                    Log.w(TAG,"DOWNLOAD_CONTACTS Failure" + e.toString());
169                }
170                break;
171
172            default:
173                Log.w(TAG,"Received Unexpected Message");
174        }
175        return;
176    }
177
178    public void abort() {
179        // Perform forced cleanup, it is ok if the handler throws an exception this will free the
180        // handler to complete what it is doing and finish with cleanup.
181        closeSocket();
182        this.getLooper().getThread().interrupt();
183    }
184
185    private void closeSocket() {
186        try {
187            if (mSocket != null) {
188                mSocket.close();
189                mSocket = null;
190            }
191        } catch (IOException e) {
192            Log.e(TAG, "Error when closing socket", e);
193            mSocket = null;
194        }
195    }
196    void downloadCallLog(String path) {
197        try {
198            BluetoothPbapRequestPullPhoneBook request =
199                    new BluetoothPbapRequestPullPhoneBook(path,mAccount,0,VCARD_TYPE_21,0,0);
200            request.execute(mObexSession);
201            CallLogPullRequest processor =
202                    new CallLogPullRequest(mPbapClientStateMachine.getContext(),path);
203            processor.setResults(request.getList());
204            processor.onPullComplete();
205        } catch (IOException e) {
206            Log.w(TAG,"Download call log failure");
207        }
208    }
209
210    private boolean addAccount(Account account) {
211        if (mAccountManager.addAccountExplicitly(account, null, null)) {
212            if (DBG) {
213                Log.d(TAG, "Added account " + mAccount);
214            }
215            return true;
216        }
217        return false;
218    }
219
220    private void removeAccount(Account acc) {
221        if (mAccountManager.removeAccountExplicitly(acc)) {
222            if (DBG) {
223                Log.d(TAG, "Removed account " + acc);
224            }
225        } else {
226            Log.e(TAG, "Failed to remove account " + mAccount);
227        }
228    }
229}
230