Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 27b77c8a authored by Joseph Pirozzo's avatar Joseph Pirozzo Committed by Sanket Agarwal
Browse files

PBAPClient State Machine

Refactor PBAP Client code to function as a state machine.  By
consolidating threads and state tracking there is less duplication and
less opportunity for conflicting combinations of state.

bug: 28249138
Change-Id: I20de8041b83024a03bfbb60102c44b4e26579553
(cherry picked from commit 427695a7992c8fc3fb503794bbaff1f705451133)
parent 0bfb869d
Loading
Loading
Loading
Loading
+157 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.bluetooth.pbapclient;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothUuid;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.util.Log;

import java.io.IOException;

import javax.obex.ClientSession;
import javax.obex.HeaderSet;
import javax.obex.ResponseCodes;

/* Bluetooth/pbapclient/PbapClientConnectionHandler is responsible
 * for connecting, disconnecting and downloading contacts from the
 * PBAP PSE when commanded. It receives all direction from the
 * controlling state machine.
 */
class PbapClientConnectionHandler extends Handler {
    static final String TAG = "PBAP PCE handler";
    static final boolean DBG = true;
    static final int MSG_CONNECT = 1;
    static final int MSG_DISCONNECT = 2;
    static final int MSG_DOWNLOAD = 3;

    private static final byte[] PBAP_TARGET = new byte[] {
            0x79, 0x61, 0x35, (byte) 0xf0, (byte) 0xf0, (byte) 0xc5, 0x11, (byte) 0xd8, 0x09, 0x66,
            0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
    };

    public static final String PB_PATH = "telecom/pb.vcf";
    public static final String MCH_PATH = "telecom/mch.vcf";
    public static final byte VCARD_TYPE_21 = 0;

    private BluetoothSocket mSocket;
    private final BluetoothAdapter mAdapter;
    private final BluetoothDevice mDevice;
    private ClientSession mObexSession;
    private BluetoothPbapObexAuthenticator mAuth = null;
    private final PbapClientStateMachine mPbapClientStateMachine;

    PbapClientConnectionHandler(Looper looper, PbapClientStateMachine stateMachine,
            BluetoothDevice device) {
        super(looper);
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mDevice = device;
        mPbapClientStateMachine = stateMachine;
        mAuth = new BluetoothPbapObexAuthenticator(this);
    }

    @Override
    public void handleMessage(Message msg) {
        if (DBG) Log.d(TAG,"Handling Message = " +msg.what);
        switch(msg.what) {
            case MSG_CONNECT:
                boolean connectionSuccessful = false;

                try {
                    /* To establish a connection first open a socket, establish a OBEX Transport
                     * abstraction, establish a Bluetooth Authenticator, and finally attempt to
                     * connect via an OBEX session */
                    mSocket = mDevice.createRfcommSocketToServiceRecord(
                            BluetoothUuid.PBAP_PSE.getUuid());
                    mSocket.connect();

                    BluetoothPbapObexTransport transport;
                    transport = new BluetoothPbapObexTransport(mSocket);

                    mObexSession  = new ClientSession(transport);
                    mObexSession.setAuthenticator(mAuth);

                    HeaderSet connectionRequest = new HeaderSet();
                    connectionRequest.setHeader(HeaderSet.TARGET, PBAP_TARGET);
                    HeaderSet connectionResponse = mObexSession.connect(connectionRequest);

                    connectionSuccessful = (connectionResponse.getResponseCode() ==
                            ResponseCodes.OBEX_HTTP_OK);
                    if (DBG) Log.d(TAG,"Success = " + Boolean.toString(connectionSuccessful));
                } catch (IOException e) {
                    Log.w(TAG,"CONNECT Failure " + e.toString());
                    closeSocket();
                }

                if (connectionSuccessful) {
                    mPbapClientStateMachine.obtainMessage(
                            PbapClientStateMachine.MSG_CONNECTION_COMPLETE).sendToTarget();
                } else {
                    mPbapClientStateMachine.obtainMessage(
                            PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget();
                }
                break;

            case MSG_DISCONNECT:
                try {
                    if (mObexSession != null) {
                        mObexSession.disconnect(null);
                    }
                } catch (IOException e) {
                    Log.w(TAG,"DISCONNECT Failure " + e.toString());
                }
                mPbapClientStateMachine.obtainMessage(
                        PbapClientStateMachine.MSG_CONNECTION_CLOSED).sendToTarget();
                break;

            case MSG_DOWNLOAD:
                try {
                    BluetoothPbapRequestPullPhoneBook request =
                            new BluetoothPbapRequestPullPhoneBook(PB_PATH, null, 0, VCARD_TYPE_21,
                            0, 0);
                    request.execute(mObexSession);
                    if (DBG) Log.d(TAG,"Download success? " + request.isSuccess());
                } catch (IOException e) {
                    Log.w(TAG,"DOWNLOAD_CONTACTS Failure" + e.toString());
                }
                break;
            default:
                Log.w(TAG,"Received Unexpected Message");
        }
        return;
    }

    public void abort() {
        closeSocket();
    }

    private void closeSocket() {
        try {
            if (mSocket != null) {
                mSocket.close();
                mSocket = null;
            }
        } catch (IOException e) {
            Log.e(TAG, "Error when closing socket", e);
        }
    }
}
+34 −53
Original line number Diff line number Diff line
@@ -41,7 +41,7 @@ import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.Utils;
import com.android.vcard.VCardEntry;


import java.lang.IllegalArgumentException;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.ArrayList;
@@ -56,9 +56,7 @@ import java.util.HashMap;
public class PbapClientService extends ProfileService {
    private static final boolean DBG = false;
    private static final String TAG = "PbapClientService";
    private PbapPCEClient mClient;
    private HandlerThread mHandlerThread;
    private AccountManager mAccountManager;
    private PbapClientStateMachine mPbapClientStateMachine;
    private static PbapClientService sPbapClientService;
    private PbapBroadcastReceiver mPbapBroadcastReceiver = new PbapBroadcastReceiver();

@@ -68,43 +66,39 @@ public class PbapClientService extends ProfileService {
    }

    @Override
    public synchronized IProfileServiceBinder initBinder() {
    public IProfileServiceBinder initBinder() {
        return new BluetoothPbapClientBinder(this);
    }

    @Override
    protected synchronized boolean start() {
    protected boolean start() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
        try {
            registerReceiver(mPbapBroadcastReceiver, filter);
        } catch (Exception e) {
            Log.w(TAG,"Unable to register pbapclient receiver",e);
        }
        mClient = new PbapPCEClient(this);
        mAccountManager = AccountManager.get(this);
        mPbapClientStateMachine = new PbapClientStateMachine(this);
        setPbapClientService(this);
        mClient.start();
        mPbapClientStateMachine.start();
        return true;
    }

    @Override
    protected synchronized boolean stop() {
    protected boolean stop() {
        try {
            unregisterReceiver(mPbapBroadcastReceiver);
        } catch (Exception e) {
            Log.w(TAG,"Unable to unregister sap receiver",e);
        }
        if (mClient != null) {
            mClient.disconnect(null);
            Log.w(TAG,"Unable to unregister pbapclient receiver",e);
        }
        mPbapClientStateMachine.disconnect(null);
        return true;
    }

    @Override
    protected synchronized boolean cleanup() {
        sPbapClientService = null;
    protected boolean cleanup() {
        clearPbapClientService();
        return true;
    }

@@ -113,12 +107,7 @@ public class PbapClientService extends ProfileService {
        public void onReceive(Context context, Intent intent) {
            Log.v(TAG, "onReceive");
            String action = intent.getAction();
            if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
                  BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                  if(getPriority(device) >= BluetoothProfile.PRIORITY_ON) {
                      connect(device);
                  }
            } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
            if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                disconnect(device);
            }
@@ -252,30 +241,35 @@ public class PbapClientService extends ProfileService {
        }
    }

    private static synchronized void clearPbapClientService() {
        sPbapClientService = null;
    }

    public boolean connect(BluetoothDevice device) {
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                "Need BLUETOOTH ADMIN permission");
        if (device == null) throw new IllegalArgumentException("Null device");
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
        Log.d(TAG,"Received request to ConnectPBAPPhonebook " + device.getAddress());
        int connectionState = mClient.getConnectionState();
        int connectionState = mPbapClientStateMachine.getConnectionState();
        if (connectionState == BluetoothProfile.STATE_CONNECTED ||
                connectionState == BluetoothProfile.STATE_CONNECTING) {
            Log.w(TAG,"Received connect request while already connecting/connected.");
            return false;
        }
        if (getPriority(device) > BluetoothProfile.PRIORITY_OFF) {
            mClient.connect(device);
            mPbapClientStateMachine.connect(device);
            return true;
        }
        return false;
    }

    private boolean disconnect(BluetoothDevice device) {
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                "Need BLUETOOTH ADMIN permission");
        mClient.disconnect(device);
    boolean disconnect(BluetoothDevice device) {
        if (device == null) throw new IllegalArgumentException("Null device");
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission");
        mPbapClientStateMachine.disconnect(device);
        return true;
    }

    private List<BluetoothDevice> getConnectedDevices() {
    public List<BluetoothDevice> getConnectedDevices() {
        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        int[] desiredStates = {BluetoothProfile.STATE_CONNECTED};
        return getDevicesMatchingConnectionStates(desiredStates);
@@ -283,31 +277,18 @@ public class PbapClientService extends ProfileService {

    private List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        int clientState = mClient.getConnectionState();
        Log.d(TAG,"getDevicesMatchingConnectionStates " + Arrays.toString(states) + " == " + clientState);
        List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>();
        for (int state : states) {
            if (clientState == state) {
                BluetoothDevice currentDevice = mClient.getDevice();
                if (currentDevice != null) {
                    deviceList.add(currentDevice);
                }
            }
        }
        return deviceList;
        return mPbapClientStateMachine.getDevicesMatchingConnectionStates(states);
    }

    int getConnectionState(BluetoothDevice device) {
        if (device == null) throw new IllegalArgumentException("Null device");
        enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
        if (device == mClient.getDevice()) {
            return mClient.getConnectionState();
        }
        return BluetoothProfile.STATE_DISCONNECTED;
        return mPbapClientStateMachine.getConnectionState(device);
    }

    public boolean setPriority(BluetoothDevice device, int priority) {
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                "Need BLUETOOTH_ADMIN permission");
        if (device == null) throw new IllegalArgumentException("Null device");
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
        Settings.Global.putInt(getContentResolver(),
                Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
                priority);
@@ -318,8 +299,8 @@ public class PbapClientService extends ProfileService {
    }

    public int getPriority(BluetoothDevice device) {
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                "Need BLUETOOTH_ADMIN permission");
        if (device == null) throw new IllegalArgumentException("Null device");
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
        int priority = Settings.Global.getInt(getContentResolver(),
                Settings.Global.getBluetoothPbapClientPriorityKey(device.getAddress()),
                BluetoothProfile.PRIORITY_UNDEFINED);
+373 −0

File added.

Preview size limit exceeded, changes collapsed.