/* * Copyright (C) 2014 Samsung System LSI * * 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.tests; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import javax.obex.HeaderSet; import javax.obex.ObexTransport; import javax.obex.Operation; import javax.obex.ResponseCodes; import javax.obex.ServerRequestHandler; import junit.framework.Assert; import android.annotation.TargetApi; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; 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.LocalServerSocket; import android.net.LocalSocket; import android.os.Build; import android.os.Debug; import android.os.ParcelUuid; import android.test.AndroidTestCase; import android.util.Log; import com.android.bluetooth.BluetoothObexTransport; import com.android.bluetooth.sdp.SdpManager; import com.android.bluetooth.tests.TestSequencer.OPTYPE; /** * Test either using the reference ril without a modem, or using a RIL implementing the * BT SAP API, by providing the rild-bt socket as well as the extended API functions for SAP. * */ @TargetApi(Build.VERSION_CODES.KITKAT) public class ObexTest extends AndroidTestCase implements ITestSequenceConfigurator { protected static String TAG = "ObexTest"; protected static final boolean D = true; protected static final boolean TRACE = false; protected static final boolean DELAY_PASS_30_SEC = false; public static final long PROGRESS_INTERVAL_MS = 1000; private static final ObexTestParams defaultParams = new ObexTestParams(2*8092, 0, 2*1024*1024); private static final ObexTestParams throttle100Params = new ObexTestParams(2*8092, 100000, 1024*1024); private static final ObexTestParams smallParams = new ObexTestParams(2*8092, 0, 2*1024); private static final ObexTestParams hugeParams = new ObexTestParams(2*8092, 0, 100*1024*1024); private static final int SMALL_OPERATION_COUNT = 1000; private static final int CONNECT_OPERATION_COUNT = 4500; private static final int L2CAP_PSM = 29; /* If SDP is not used */ private static final int RFCOMM_CHANNEL = 29; /* If SDP is not used */ //public static final String SERVER_ADDRESS = "10:68:3F:5E:F9:2E"; public static final String SERVER_ADDRESS = "F8:CF:C5:A8:70:7E"; private static final String SDP_SERVER_NAME = "Samsung Server"; private static final String SDP_CLIENT_NAME = "Samsung Client"; private static final long SDP_FEATURES = 0x87654321L; /* 32 bit */ private static final int SDP_MSG_TYPES = 0xf1; /* 8 bit */ private static final int SDP_MAS_ID = 0xCA; /* 8 bit */ private static final int SDP_VERSION = 0xF0C0; /* 16 bit */ public static final ParcelUuid SDP_UUID_OBEX_MAS = BluetoothUuid.MAS; private static int sSdpHandle = -1; private static final ObexTestDataHandler sDataHandler = new ObexTestDataHandler("(Client)"); private static final ISeqStepValidator sResponseCodeValidator = new ResponseCodeValidator(); private static final ISeqStepValidator sDataValidator = new DataValidator(); private enum SequencerType { SEQ_TYPE_PAYLOAD, SEQ_TYPE_CONNECT_DISCONNECT } private Context mContext = null; private int mChannelType = 0; public ObexTest() { super(); } /** * Test that a connection can be established. * WARNING: The performance of the pipe implementation is not good. I'm only able to get a * throughput of around 220 kbyte/sec - less that when using Bluetooth :-) * UPDATE: Did a local socket implementation below to replace this... * This has a throughput of more than 4000 kbyte/s */ public void testLocalPipes() { mContext = this.getContext(); System.out.println("Setting up pipes..."); PipedInputStream clientInStream = null; PipedOutputStream clientOutStream = null; PipedInputStream serverInStream = null; PipedOutputStream serverOutStream = null; ObexPipeTransport clientTransport = null; ObexPipeTransport serverTransport = null; try { /* Create and interconnect local pipes for transport */ clientInStream = new PipedInputStream(5*8092); clientOutStream = new PipedOutputStream(); serverInStream = new PipedInputStream(clientOutStream, 5*8092); serverOutStream = new PipedOutputStream(clientInStream); /* Create the OBEX transport objects to wrap the pipes - enable SRM */ clientTransport = new ObexPipeTransport(clientInStream, clientOutStream, true); serverTransport = new ObexPipeTransport(serverInStream, serverOutStream, true); TestSequencer sequencer = createBtPayloadTestSequence(clientTransport, serverTransport); //Debug.startMethodTracing("ObexTrace"); assertTrue(sequencer.run(mContext)); //Debug.stopMethodTracing(); } catch (IOException e) { Log.e(TAG, "IOException", e); } } /** * Run the test sequence using a local socket. * Throughput around 4000 kbyte/s - with a larger OBEX package size. */ public void testLocalSockets() { mContext = this.getContext(); System.out.println("Setting up sockets..."); try { /* Create and interconnect local pipes for transport */ LocalServerSocket serverSock = new LocalServerSocket("com.android.bluetooth.tests.sock"); LocalSocket clientSock = new LocalSocket(); LocalSocket acceptSock; clientSock.connect(serverSock.getLocalSocketAddress()); acceptSock = serverSock.accept(); /* Create the OBEX transport objects to wrap the pipes - enable SRM */ ObexPipeTransport clientTransport = new ObexPipeTransport(clientSock.getInputStream(), clientSock.getOutputStream(), true); ObexPipeTransport serverTransport = new ObexPipeTransport(acceptSock.getInputStream(), acceptSock.getOutputStream(), true); TestSequencer sequencer = createBtPayloadTestSequence(clientTransport, serverTransport); //Debug.startMethodTracing("ObexTrace"); assertTrue(sequencer.run(mContext)); //Debug.stopMethodTracing(); clientSock.close(); acceptSock.close(); serverSock.close(); } catch (IOException e) { Log.e(TAG, "IOException", e); } } /* Create a sequence of put/get operations with different payload sizes */ private TestSequencer createBtPayloadTestSequence(ObexTransport clientTransport, ObexTransport serverTransport) throws IOException { TestSequencer sequencer = new TestSequencer(clientTransport, serverTransport, this); SeqStep step; step = sequencer.addStep(OPTYPE.CONNECT, sResponseCodeValidator); if(false){ step = sequencer.addStep(OPTYPE.PUT, sDataValidator); step.mParams = defaultParams; step.mUseSrm = true; step = sequencer.addStep(OPTYPE.GET, sDataValidator); step.mParams = defaultParams; step.mUseSrm = true; step = sequencer.addStep(OPTYPE.PUT, sDataValidator); step.mParams = throttle100Params; step.mUseSrm = true; step = sequencer.addStep(OPTYPE.GET, sDataValidator); step.mParams = throttle100Params; step.mUseSrm = true; for(int i=0; i= 0) { SdpManager.getDefaultManager().removeSdpRecord(sSdpHandle); } Log.d(TAG, "Creating record with rfcomm channel: " + rfcommChannel + " and l2cap channel: " + l2capPsm); sSdpHandle = SdpManager.getDefaultManager().createMapMasRecord(SDP_SERVER_NAME, SDP_MAS_ID, rfcommChannel, l2capPsm, SDP_VERSION, SDP_MSG_TYPES, (int)(SDP_FEATURES & 0xffffffff)); } else { Log.d(TAG, "SKIP creation of record with rfcomm channel: " + rfcommChannel + " and l2cap channel: " + l2capPsm); } return serverSocket; } public static void removeSdp() { if(sSdpHandle > 0) { SdpManager.getDefaultManager().removeSdpRecord(sSdpHandle); sSdpHandle = -1; } } /** * Server side of a two device Bluetooth test of OBEX */ private void testBtServer(int type, boolean useSdp, SequencerType sequencerType) { mContext = this.getContext(); Log.d(TAG,"Starting BT Server..."); if(TRACE) Debug.startMethodTracing("ServerSide"); try { BluetoothServerSocket serverSocket=createServerSocket(type, useSdp); Log.i(TAG, "Waiting for client to connect"); BluetoothSocket socket = serverSocket.accept(); Log.i(TAG, "Client connected"); BluetoothObexTransport serverTransport = new BluetoothObexTransport(socket); TestSequencer sequencer = null; switch(sequencerType) { case SEQ_TYPE_CONNECT_DISCONNECT: sequencer = createBtConnectTestSequence(null, serverTransport); break; case SEQ_TYPE_PAYLOAD: sequencer = createBtPayloadTestSequence(null, serverTransport); break; default: fail("Invalid sequencer type"); break; } //Debug.startMethodTracing("ObexTrace"); assertTrue(sequencer.run(mContext)); //Debug.stopMethodTracing(); // Same as below... serverTransport.close(); // This is done by the obex server socket.close(); serverSocket.close(); removeSdp(); sequencer.shutdown(); } catch (IOException e) { Log.e(TAG, "IOException", e); } if(TRACE) Debug.stopMethodTracing(); if(DELAY_PASS_30_SEC) { Log.i(TAG, "\n\n\nTest done - please fetch logs within 30 seconds...\n\n\n"); try { Thread.sleep(30000); } catch (InterruptedException e) {} } Log.i(TAG, "Test done."); } /** * Enable Bluetooth and connect to a server socket * @param type * @param useSdp * @param context * @return * @throws IOException */ static public BluetoothSocket connectClientSocket(int type, boolean useSdp, Context context) throws IOException { int rfcommChannel = RFCOMM_CHANNEL; int l2capPsm = L2CAP_PSM; BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); if(bt == null) { Log.e(TAG,"No Bluetooth Device!"); assertTrue(false); } BluetoothTestUtils.enableBt(bt); BluetoothDevice serverDevice = bt.getRemoteDevice(SERVER_ADDRESS); if(useSdp == true) { SdpMasRecord record = clientAwaitSdp(serverDevice, context); rfcommChannel = record.getRfcommCannelNumber(); l2capPsm = record.getL2capPsm(); } BluetoothSocket socket = null; if(type == BluetoothSocket.TYPE_L2CAP) { socket = serverDevice.createL2capSocket(l2capPsm); } else if(type == BluetoothSocket.TYPE_RFCOMM) { socket = serverDevice.createRfcommSocket(rfcommChannel); } else { fail("Invalid transport type!"); } socket.connect(); return socket; } /** * Test that a connection can be established. */ private void testBtClient(int type, boolean useSdp, SequencerType sequencerType) { mContext = this.getContext(); mChannelType = type; BluetoothSocket socket = null; System.out.println("Starting BT Client..."); if(TRACE) Debug.startMethodTracing("ClientSide"); try { socket = connectClientSocket(type, useSdp, mContext); BluetoothObexTransport clientTransport = new BluetoothObexTransport(socket); TestSequencer sequencer = null; switch(sequencerType) { case SEQ_TYPE_CONNECT_DISCONNECT: sequencer = createBtConnectTestSequence(clientTransport, null); break; case SEQ_TYPE_PAYLOAD: sequencer = createBtPayloadTestSequence(clientTransport, null); break; default: fail("Invalid test type"); break; } //Debug.startMethodTracing("ObexTrace"); assertTrue(sequencer.run(mContext)); //Debug.stopMethodTracing(); socket.close(); // Only the streams are closed by the obex client sequencer.shutdown(); } catch (IOException e) { Log.e(TAG, "IOException", e); } if(TRACE) Debug.stopMethodTracing(); if(DELAY_PASS_30_SEC) { Log.i(TAG, "\n\n\nTest done - please fetch logs within 30 seconds...\n\n\n"); try { Thread.sleep(30000); } catch (InterruptedException e) {} } Log.i(TAG, "Test done."); } /* Using an anonymous class is not efficient, but keeps a tight code structure. */ static class SdpBroadcastReceiver extends BroadcastReceiver { private SdpMasRecord mMasRecord; /* A non-optimal way of setting an object reference from a anonymous class. */ final CountDownLatch mLatch; public SdpBroadcastReceiver(CountDownLatch latch) { mLatch = latch; } SdpMasRecord getMasRecord() { return mMasRecord; } @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "onReceive"); String action = intent.getAction(); if (action.equals(BluetoothDevice.ACTION_SDP_RECORD)){ Log.v(TAG, "Received ACTION_SDP_RECORD."); ParcelUuid uuid = intent.getParcelableExtra(BluetoothDevice.EXTRA_UUID); Log.v(TAG, "Received UUID: " + uuid.toString()); Log.v(TAG, "existing UUID: " + SDP_UUID_OBEX_MAS.toString()); if(uuid.toString().equals(SDP_UUID_OBEX_MAS.toString())) { assertEquals(SDP_UUID_OBEX_MAS.toString(), uuid.toString()); Log.v(TAG, " -> MAS UUID in result."); SdpMasRecord record = intent.getParcelableExtra( BluetoothDevice.EXTRA_SDP_RECORD); assertNotNull(record); Log.v(TAG, " -> record: "+record); if(record.getServiceName().equals(SDP_SERVER_NAME)) { assertEquals(((long)record.getSupportedFeatures()) &0xffffffffL, SDP_FEATURES); assertEquals(record.getSupportedMessageTypes(), SDP_MSG_TYPES); assertEquals(record.getProfileVersion(), SDP_VERSION); assertEquals(record.getServiceName(), SDP_SERVER_NAME); assertEquals(record.getMasInstanceId(), SDP_MAS_ID); int status = intent.getIntExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, -1); Log.v(TAG, " -> status: "+status); mMasRecord = record; mLatch.countDown(); } else { Log.i(TAG, "Wrong service name (" + record.getServiceName() + ") received, still waiting..."); } } else { Log.i(TAG, "Wrong UUID received, still waiting..."); } } else { fail("Unexpected intent received???"); } } }; private static SdpMasRecord clientAwaitSdp(BluetoothDevice serverDevice, Context context) { IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothDevice.ACTION_SDP_RECORD); final CountDownLatch latch = new CountDownLatch(1); SdpBroadcastReceiver broadcastReceiver = new SdpBroadcastReceiver(latch); context.registerReceiver(broadcastReceiver, filter); serverDevice.sdpSearch(SDP_UUID_OBEX_MAS); boolean waiting = true; while(waiting == true) { try { Log.i(TAG, "SDP Search requested - awaiting result..."); latch.await(); Log.i(TAG, "SDP Search reresult received - continueing."); waiting = false; } catch (InterruptedException e) { Log.w(TAG, "Interrupted witle waiting - keep waiting.", e); waiting = true; } } context.unregisterReceiver(broadcastReceiver); return broadcastReceiver.getMasRecord(); } @Override public ServerRequestHandler getObexServer(ArrayList sequence, CountDownLatch stopLatch) { return new ObexTestServer(sequence, stopLatch); } }