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

Commit 816621a4 authored by vnori's avatar vnori Committed by Gerrit Code Review
Browse files

Merge "Allow multiple connections on MAP profile."

parents 2c0764d2 31c9ac6b
Loading
Loading
Loading
Loading
+253 −36
Original line number Diff line number Diff line
@@ -21,9 +21,17 @@ import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothMapClient;
import android.bluetooth.SdpMasRecord;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.ParcelUuid;
import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
import android.util.Log;

import com.android.bluetooth.Utils;
@@ -31,8 +39,11 @@ import com.android.bluetooth.btservice.ProfileService;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class MapClientService extends ProfileService {
    private static final String TAG = "MapClientService";
@@ -40,15 +51,15 @@ public class MapClientService extends ProfileService {
    static final boolean DBG = false;
    static final boolean VDBG = false;

    private static final int MAXIMUM_CONNECTED_DEVICES = 1;
    static final int MAXIMUM_CONNECTED_DEVICES = 4;

    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;

    MceStateMachine mMceStateMachine;
    private Map<BluetoothDevice, MceStateMachine> mMapInstanceMap = new ConcurrentHashMap<>(1);
    private MnsService mMnsServer;
    private BluetoothAdapter mAdapter;
    private static MapClientService sMapClientService;

    private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver();

    public static synchronized MapClientService getMapClientService() {
        if (sMapClientService != null && sMapClientService.isAvailable()) {
@@ -76,28 +87,126 @@ public class MapClientService extends ProfileService {
        } else {
            if (DBG) {
                if (sMapClientService == null) {
                    Log.d(TAG, "setA2dpService(): service not available");
                    Log.d(TAG, "MapClientService service not available");
                } else if (!sMapClientService.isAvailable()) {
                    Log.d(TAG, "setA2dpService(): service is cleaning up");
                    Log.d(TAG, "MapClientService service is cleaning up");
                }
            }
        }
    }

    private static synchronized void clearMapClientService() {
        sMapClientService = null;
    }

    @VisibleForTesting
    Map<BluetoothDevice, MceStateMachine> getInstanceMap() {
        return mMapInstanceMap;
    }

    /**
     * Connect the given Bluetooth device.
     *
     * @param device
     * @return true if connection is successful, false otherwise.
     */
    public synchronized boolean connect(BluetoothDevice device) {
        Log.d(TAG, "MAP Mce connect " + device.toString());
        return mMceStateMachine.connect(device);
        if (device == null) {
            throw new IllegalArgumentException("Null device");
        }
        if (DBG) {
            StringBuilder sb = new StringBuilder();
            dump(sb);
            Log.d(TAG, "MAP connect device: " + device
                    + ", InstanceMap start state: " + sb.toString());
        }
        MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
        if (mapStateMachine == null) {
            // a map state machine instance doesn't exist yet, create a new one if we can.
            if (mMapInstanceMap.size() < MAXIMUM_CONNECTED_DEVICES) {
                addDeviceToMapAndConnect(device);
                return true;
            } else {
                // Maxed out on the number of allowed connections.
                // see if some of the current connections can be cleaned-up, to make room.
                removeUncleanAccounts();
                if (mMapInstanceMap.size() < MAXIMUM_CONNECTED_DEVICES) {
                    addDeviceToMapAndConnect(device);
                    return true;
                } else {
                    Log.e(TAG, "Maxed out on the number of allowed MAP connections. "
                            + "Connect request rejected on " + device);
                    return false;
                }
            }
        }

        // statemachine already exists in the map.
        int state = getConnectionState(device);
        if (state == BluetoothProfile.STATE_CONNECTED
                || state == BluetoothProfile.STATE_CONNECTING) {
            Log.w(TAG, "Received connect request while already connecting/connected.");
            return false;
        }

        // Statemachine exists but not in connecting or connected state! it should
        // have been removed form the map. lets get rid of it and add a new one.
        if (DBG) {
            Log.d(TAG, "Statemachine exists for a device in unexpected state: " + state);
        }
        mMapInstanceMap.remove(mapStateMachine);
        addDeviceToMapAndConnect(device);
        if (DBG) {
            StringBuilder sb = new StringBuilder();
            dump(sb);
            Log.d(TAG, "MAP connect device: " + device
                    + ", InstanceMap end state: " + sb.toString());
        }
        return true;
    }

    private synchronized void addDeviceToMapAndConnect(BluetoothDevice device) {
        // When creating a new statemachine, its state is set to CONNECTING - which will trigger
        // connect.
        MceStateMachine mapStateMachine = new MceStateMachine(this, device);
        mMapInstanceMap.put(device, mapStateMachine);
    }

    public synchronized boolean disconnect(BluetoothDevice device) {
        Log.d(TAG, "MAP Mce disconnect " + device.toString());
        return mMceStateMachine.disconnect(device);
        if (DBG) {
            StringBuilder sb = new StringBuilder();
            dump(sb);
            Log.d(TAG, "MAP disconnect device: " + device
                    + ", InstanceMap start state: " + sb.toString());
        }
        MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
        // a map state machine instance doesn't exist. maybe it is already gone?
        if (mapStateMachine == null) {
            return false;
        }
        int connectionState = mapStateMachine.getState();
        if (connectionState != BluetoothProfile.STATE_CONNECTED
                && connectionState != BluetoothProfile.STATE_CONNECTING) {
            return false;
        }
        mapStateMachine.disconnect();
        if (DBG) {
            StringBuilder sb = new StringBuilder();
            dump(sb);
            Log.d(TAG, "MAP disconnect device: " + device
                    + ", InstanceMap start state: " + sb.toString());
        }
        return true;
    }

    public List<BluetoothDevice> getConnectedDevices() {
        return getDevicesMatchingConnectionStates(new int[]{BluetoothAdapter.STATE_CONNECTED});
    }

    MceStateMachine getMceStateMachineForDevice(BluetoothDevice device) {
        return mMapInstanceMap.get(device);
    }

    public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
        Log.d(TAG, "getDevicesMatchingConnectionStates" + Arrays.toString(states));
        List<BluetoothDevice> deviceList = new ArrayList<>();
@@ -117,11 +226,10 @@ public class MapClientService extends ProfileService {
    }

    public synchronized int getConnectionState(BluetoothDevice device) {
        if (mMceStateMachine != null && device.equals(mMceStateMachine.getDevice())) {
            return mMceStateMachine.getState();
        } else {
            return BluetoothProfile.STATE_DISCONNECTED;
        }
        MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
        // a map state machine instance doesn't exist yet, create a new one if we can.
        return (mapStateMachine == null) ? BluetoothProfile.STATE_DISCONNECTED
                : mapStateMachine.getState();
    }

    public boolean setPriority(BluetoothDevice device, int priority) {
@@ -142,12 +250,9 @@ public class MapClientService extends ProfileService {

    public synchronized boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
            PendingIntent sentIntent, PendingIntent deliveredIntent) {
        if (mMceStateMachine != null && device.equals(mMceStateMachine.getDevice())) {
            return mMceStateMachine.sendMapMessage(contacts, message, sentIntent, deliveredIntent);
        } else {
            return false;
        }

        MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
        return mapStateMachine != null
                && mapStateMachine.sendMapMessage(contacts, message, sentIntent, deliveredIntent);
    }

    @Override
@@ -157,20 +262,21 @@ public class MapClientService extends ProfileService {

    @Override
    protected boolean start() {
        if (DBG) {
            Log.d(TAG, "start()");
        }
        Log.e(TAG, "start()");
        setService(this);

        if (mMnsServer == null) {
            mMnsServer = new MnsService(this);
        }
        if (mMceStateMachine == null) {
            mMceStateMachine = new MceStateMachine(this);
            mMnsServer = MapUtils.newMnsServiceInstance(this);
        }

        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mStartError = false;

        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
        registerReceiver(mMapReceiver, filter);
        removeUncleanAccounts();
        return !mStartError;
    }

@@ -179,35 +285,88 @@ public class MapClientService extends ProfileService {
        if (DBG) {
            Log.d(TAG, "stop()");
        }
        unregisterReceiver(mMapReceiver);
        if (mMnsServer != null) {
            mMnsServer.stop();
        }
        if (mMceStateMachine.getState() == BluetoothAdapter.STATE_CONNECTED) {
            mMceStateMachine.disconnect(mMceStateMachine.getDevice());
        for (MceStateMachine stateMachine : mMapInstanceMap.values()) {
            if (stateMachine.getState() == BluetoothAdapter.STATE_CONNECTED) {
                stateMachine.disconnect();
            }
            stateMachine.doQuit();
        }
        mMceStateMachine.doQuit();
        return true;
    }

    @Override
    protected void cleanup() {
        if (DBG) {
            Log.d(TAG, "cleanup()");
            Log.d(TAG, "in Cleanup");
        }
        removeUncleanAccounts();
        clearMapClientService();
    }

    void cleanupDevice(BluetoothDevice device) {
        if (DBG) {
            StringBuilder sb = new StringBuilder();
            dump(sb);
            Log.d(TAG, "Cleanup device: " + device + ", InstanceMap start state: "
                    + sb.toString());
        }
        synchronized (mMapInstanceMap) {
            MceStateMachine stateMachine = mMapInstanceMap.get(device);
            if (stateMachine != null) {
                mMapInstanceMap.remove(device);
            }
        }
        if (DBG) {
            StringBuilder sb = new StringBuilder();
            dump(sb);
            Log.d(TAG, "Cleanup device: " + device + ", InstanceMap end state: "
                    + sb.toString());
        }
    }

    @VisibleForTesting
    void removeUncleanAccounts() {
        if (DBG) {
            StringBuilder sb = new StringBuilder();
            dump(sb);
            Log.d(TAG, "removeUncleanAccounts:InstanceMap end state: "
                    + sb.toString());
        }
        Iterator iterator = mMapInstanceMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<BluetoothDevice, MceStateMachine> profileConnection =
                    (Map.Entry) iterator.next();
            if (profileConnection.getValue().getState() == BluetoothProfile.STATE_DISCONNECTED) {
                iterator.remove();
            }
        }
        if (DBG) {
            StringBuilder sb = new StringBuilder();
            dump(sb);
            Log.d(TAG, "removeUncleanAccounts:InstanceMap end state: "
                    + sb.toString());
        }
    }

    public synchronized boolean getUnreadMessages(BluetoothDevice device) {
        if (mMceStateMachine != null && device.equals(mMceStateMachine.getDevice())) {
            return mMceStateMachine.getUnreadMessages();
        } else {
        MceStateMachine mapStateMachine = mMapInstanceMap.get(device);
        if (mapStateMachine == null) {
            return false;
        }
        return mapStateMachine.getUnreadMessages();
    }

    @Override
    public synchronized void dump(StringBuilder sb) {
    public void dump(StringBuilder sb) {
        super.dump(sb);
        println(sb, "StateMachine: " + mMceStateMachine.toString());
        ProfileService.println(sb, "# Services Connected: " + mMapInstanceMap.size());
        for (MceStateMachine stateMachine : mMapInstanceMap.values()) {
            stateMachine.dump(sb);
        }
    }

    //Binder object: Must be static class or memory leak may occur
@@ -360,4 +519,62 @@ public class MapClientService extends ProfileService {
            return service.getUnreadMessages(device);
        }
    }

    private class MapBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (DBG) {
                Log.d(TAG, "onReceive: " + action);
            }
            if (!action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)
                    && !action.equals(BluetoothDevice.ACTION_SDP_RECORD)) {
                // we don't care about this intent
                return;
            }
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            if (device == null) {
                Log.e(TAG, "broadcast has NO device param!");
                return;
            }
            if (DBG) {
                Log.d(TAG, "broadcast has device: (" + device.getAddress() + ", "
                        + device.getName() + ")");
            }
            MceStateMachine stateMachine = MapClientService.this.mMapInstanceMap.get(device);
            if (stateMachine == null) {
                Log.e(TAG, "No Statemachine found for the device from broadcast");
                return;
            }

            if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
                if (stateMachine.getState() == BluetoothProfile.STATE_CONNECTED) {
                    stateMachine.disconnect();
                }
            }

            if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)) {
                ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
                if (DBG) {
                    Log.d(TAG, "UUID of SDP: " + uuid);
                }

                if (uuid.equals(BluetoothUuid.MAS)) {
                    // Check if we have a valid SDP record.
                    SdpMasRecord masRecord =
                            intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD);
                    if (DBG) {
                        Log.d(TAG, "SDP = " + masRecord);
                    }
                    int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1);
                    if (masRecord == null) {
                        Log.w(TAG, "SDP search ended with no MAS record. Status: " + status);
                        return;
                    }
                    stateMachine.obtainMessage(MceStateMachine.MSG_MAS_SDP_DONE,
                            masRecord).sendToTarget();
                }
            }
        }
    }
}
+31 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.mapclient;

import android.support.annotation.VisibleForTesting;

class MapUtils {
    private static MnsService sMnsService = null;

    @VisibleForTesting
    static void setMnsService(MnsService service) {
        sMnsService = service;
    }

    static MnsService newMnsServiceInstance(MapClientService mapClientService) {
        return (sMnsService != null) ? new MnsService(mapClientService) : sMnsService;
    }
}
+28 −88
Original line number Diff line number Diff line
@@ -47,13 +47,9 @@ import android.bluetooth.BluetoothMapClient;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.SdpMasRecord;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Message;
import android.os.ParcelUuid;
import android.telecom.PhoneAccount;
import android.util.Log;

@@ -66,6 +62,7 @@ import com.android.vcard.VCardEntry;
import com.android.vcard.VCardProperty;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;

@@ -111,7 +108,7 @@ final class MceStateMachine extends StateMachine {
    private State mConnected;
    private State mDisconnecting;

    private BluetoothDevice mDevice;
    private final BluetoothDevice mDevice;
    private MapClientService mService;
    private MasClient mMasClient;
    private HashMap<String, Bmessage> mSentMessageLog = new HashMap<>(MAX_MESSAGES);
@@ -119,14 +116,14 @@ final class MceStateMachine extends StateMachine {
    private HashMap<Bmessage, PendingIntent> mDeliveryReceiptRequested =
            new HashMap<>(MAX_MESSAGES);
    private Bmessage.Type mDefaultMessageType = Bmessage.Type.SMS_CDMA;
    private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver();

    MceStateMachine(MapClientService service) {
    MceStateMachine(MapClientService service, BluetoothDevice device) {
        super(TAG);
        mService = service;

        mPreviousState = BluetoothProfile.STATE_DISCONNECTED;

        mDevice = device;
        mDisconnected = new Disconnected();
        mConnecting = new Connecting();
        mDisconnecting = new Disconnecting();
@@ -136,7 +133,7 @@ final class MceStateMachine extends StateMachine {
        addState(mConnecting);
        addState(mDisconnecting);
        addState(mConnected);
        setInitialState(mDisconnected);
        setInitialState(mConnecting);
        start();
    }

@@ -144,6 +141,13 @@ final class MceStateMachine extends StateMachine {
        quitNow();
    }

    @Override
    protected void onQuitting() {
        if (mService != null) {
            mService.cleanupDevice(mDevice);
        }
    }

    synchronized BluetoothDevice getDevice() {
        return mDevice;
    }
@@ -181,19 +185,11 @@ final class MceStateMachine extends StateMachine {
        return BluetoothProfile.STATE_DISCONNECTED;
    }

    public boolean connect(BluetoothDevice device) {
        if (DBG) {
            Log.d(TAG, "Connect Request " + device.getAddress());
        }
        sendMessage(MSG_CONNECT, device);
        return true;
    }

    public boolean disconnect(BluetoothDevice device) {
    public boolean disconnect() {
        if (DBG) {
            Log.d(TAG, "Disconnect Request " + device.getAddress());
            Log.d(TAG, "Disconnect Request " + mDevice.getAddress());
        }
        sendMessage(MSG_DISCONNECT, device);
        sendMessage(MSG_DISCONNECT, mDevice);
        return true;
    }

@@ -291,6 +287,11 @@ final class MceStateMachine extends StateMachine {
        }
    }

    public void dump(StringBuilder sb) {
        ProfileService.println(sb, "mCurrentDevice: " + mDevice.getAddress() + " (name = "
                + mDevice.getName() + "), StateMachine: " + this.toString());
    }

    class Disconnected extends State {
        @Override
        public void enter() {
@@ -299,24 +300,7 @@ final class MceStateMachine extends StateMachine {
            }
            onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_DISCONNECTED);
            mPreviousState = BluetoothProfile.STATE_DISCONNECTED;
        }

        @Override
        public boolean processMessage(Message message) {
            switch (message.what) {
                case MSG_CONNECT:
                    synchronized (MceStateMachine.this) {
                        mDevice = (BluetoothDevice) message.obj;
                    }
                    transitionTo(mConnecting);
                    break;

                default:
                    Log.w(TAG, "Unexpected message: " + message.what + " from state:"
                            + this.getName());
                    return NOT_HANDLED;
            }
            return HANDLED;
            quit();
        }

        @Override
@@ -333,12 +317,6 @@ final class MceStateMachine extends StateMachine {
            }
            onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_CONNECTING);

            IntentFilter filter = new IntentFilter();
            filter.addAction(BluetoothDevice.ACTION_SDP_RECORD);
            filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
            // unregisterReceiver in Disconnecting
            mService.registerReceiver(mMapReceiver, filter);

            BluetoothAdapter.getDefaultAdapter().cancelDiscovery();
            // When commanded to connect begin SDP to find the MAS server.
            mDevice.sdpSearch(BluetoothUuid.MAS);
@@ -437,11 +415,15 @@ final class MceStateMachine extends StateMachine {
                    break;

                case MSG_GET_MESSAGE_LISTING:
                    // Get latest 50 Unread messages in the last week
                    MessagesFilter filter = new MessagesFilter();
                    filter.setMessageType((byte) 0);
                    mMasClient.makeRequest(
                            new RequestGetMessagesListing((String) message.obj, 0, filter, 0, 1,
                                    0));
                    filter.setReadStatus(MessagesFilter.READ_STATUS_UNREAD);
                    Calendar calendar = Calendar.getInstance();
                    calendar.add(Calendar.DATE, -7);
                    filter.setPeriod(calendar.getTime(), null);
                    mMasClient.makeRequest(new RequestGetMessagesListing(
                            (String) message.obj, 0, filter, 0, 50, 0));
                    break;

                case MSG_MAS_REQUEST_COMPLETED:
@@ -633,7 +615,6 @@ final class MceStateMachine extends StateMachine {
                Log.d(TAG, "Enter Disconnecting: " + getCurrentMessage().what);
            }
            onConnectionStateChanged(mPreviousState, BluetoothProfile.STATE_DISCONNECTING);
            mService.unregisterReceiver(mMapReceiver);

            if (mMasClient != null) {
                mMasClient.makeRequest(new RequestSetNotificationRegistration(false));
@@ -683,45 +664,4 @@ final class MceStateMachine extends StateMachine {
        }
        sendMessage(MSG_NOTIFICATION, ev);
    }

    private class MapBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (DBG) {
                Log.d(TAG, "onReceive");
            }
            String action = intent.getAction();
            if (DBG) {
                Log.d(TAG, "onReceive: " + action);
            }
            if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                if (getDevice().equals(device) && getState() == BluetoothProfile.STATE_CONNECTED) {
                    disconnect(device);
                }
            }

            if (BluetoothDevice.ACTION_SDP_RECORD.equals(intent.getAction())) {
                ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID);
                if (DBG) {
                    Log.d(TAG, "UUID of SDP: " + uuid);
                }

                if (uuid.equals(BluetoothUuid.MAS)) {
                    // Check if we have a valid SDP record.
                    SdpMasRecord masRecord =
                            intent.getParcelableExtra(BluetoothDevice.EXTRA_SDP_RECORD);
                    if (DBG) {
                        Log.d(TAG, "SDP = " + masRecord);
                    }
                    int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1);
                    if (masRecord == null) {
                        Log.w(TAG, "SDP search ended with no MAS record. Status: " + status);
                        return;
                    }
                    obtainMessage(MceStateMachine.MSG_MAS_SDP_DONE, masRecord).sendToTarget();
                }
            }
        }
    }
}
+7 −1
Original line number Diff line number Diff line
@@ -122,7 +122,13 @@ class MnsService {
                Log.d(TAG, "onConnect" + device + " SOCKET: " + socket);
            }
            /* Signal to the service that we have received an incoming connection.*/
            MnsObexServer srv = new MnsObexServer(sContext.mMceStateMachine, sServerSockets);
            MceStateMachine stateMachine = sContext.getMceStateMachineForDevice(device);
            if (stateMachine == null) {
                Log.e(TAG, "Error: NO statemachine for device: " + device.getAddress()
                        + " (name: " + device.getName());
                return false;
            }
            MnsObexServer srv = new MnsObexServer(stateMachine, sServerSockets);
            BluetoothObexTransport transport = new BluetoothObexTransport(socket);
            try {
                new ServerSession(transport, srv, null);
+152 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading