diff options
author | kschulz <k.schulz@samsung.com> | 2015-03-17 11:47:46 +0100 |
---|---|---|
committer | Andre Eisenbach <eisenbach@google.com> | 2015-04-10 19:43:37 -0700 |
commit | 5a60e47497f21f64e6d79420dc4c56c1907df22a (patch) | |
tree | 68a80ce2c49692ab9b50204ba8ee8a6aa238df02 /tests | |
parent | 0dcecb2cfd921916ed586183d64ec9fd250a6e4c (diff) | |
download | android_packages_apps_Bluetooth-5a60e47497f21f64e6d79420dc4c56c1907df22a.tar.gz android_packages_apps_Bluetooth-5a60e47497f21f64e6d79420dc4c56c1907df22a.tar.bz2 android_packages_apps_Bluetooth-5a60e47497f21f64e6d79420dc4c56c1907df22a.zip |
Update to Bluetooth MAP 1.2 (server)
- Change folder name lookup to a map
Replaced the arrays used to convert mailbox ID/msg type to a folder name with a static map.
This is to avoid null pointer exception for unknown values, and to catch any changes in
the ID/type values at compile time in stead of runtime.
Bug-id:16874441
- Added Instance Information support and Extended Event support.
Still missing integration wiht SDP MAP feature bit mask support
- Adding Abstract implementation to support conversations
- added IM account handling, IM type definition, Application paramenters.
- addedgetConversactionList functionality
- added method to strip encoding in headers
- Fixed messagelist showing both email address and name in the name fields.
- Fixed Index out of bounds exception was hit when the subject contained invalid chars.
- Added functionality to support the getConversationListReq
Works for SMS/MMS, Email and IM
For Email/IM it depends on the convoContact table in the contract.
For SMS/MMS it uses the contact number+ name if available in contact database.
- Added new parameters to msgListing also in contract class
- Added Test framework for "near system level" tests
Currently only includes an entry point for single device tests.
- Added support for setOwnerStatus
- Added support for vcard type X-BT-UID
- Introduced type SignedLongLong to handle 128 bit values which needs to be handled as hex-strings.
- Added convocontact notification events for IM
- Added support for IM getMessage
- Added setEventFilter function.
- Added event filtering before enquing an event to be send.
- Added selective observers, depending on the active filter.
- Fixed timestamp to be from seconds to seconds (not from milisec)
- Fixed version number in bMessage if remote featurebit is set for v 1.1
- Added content encoding to QP for text that are not USACII
- Corrected the addresses in to/from for IM messages
- Added btuid and btuci to vcard
- Fixed (some) longlines
- Added extendedData support (empty when sending, just logging when receiving)
- Fixed Email folderName compairison changed to ignore case
- Fixed problem with names containing "null"
- BluetoothMapbMessageMms changed to BluetoothMapbMessageMime
- Fixrf addOriginator in getMessage request
- Add missing subjects in events for SMS
- Don't send ReadStatusChanged when pushing a message
- Temp way of adding names/uci to IM msg listing
- Added messageHandle filtering in msgListing
- Convolisting parameter mask support
- Added support for using handle when filtering in root folder during msgLising
- Added subject to event in sms
- Fixed so attribute_mime_type is only sent when parameter is requested
- Fixed feature bit check to messageListing version
- Fixed leaking cursors
- Added support for database identifier
- Added folder and conversation version counters
Change-Id: I4d2954b795aa7ed2a41dd034384da30f240b518f
Diffstat (limited to 'tests')
24 files changed, 3110 insertions, 501 deletions
diff --git a/tests/Android.mk b/tests/Android.mk index ca30b0848..612cb990e 100755 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -5,7 +5,7 @@ include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_CERTIFICATE := platform -LOCAL_JAVA_LIBRARIES := javax.obex android.test.runner +LOCAL_JAVA_LIBRARIES := javax.obex android.test.runner telephony-common mms-common LOCAL_STATIC_JAVA_LIBRARIES := com.android.emailcommon # Include all test java files. diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml index 657c85840..cf128db84 100755 --- a/tests/AndroidManifest.xml +++ b/tests/AndroidManifest.xml @@ -11,6 +11,7 @@ <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> + <uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> diff --git a/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java b/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java index 9e318c63d..15e1bdca8 100644 --- a/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java +++ b/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java @@ -1,102 +1,887 @@ package com.android.bluetooth.tests; -import android.test.AndroidTestCase; -import android.util.Log; -import android.database.Cursor; -import android.content.Context; -import android.content.ContentResolver; - +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; +import java.util.LinkedHashMap; -import com.android.bluetooth.map.BluetoothMapContent; -import com.android.bluetooth.map.BluetoothMapContentObserver; +import android.annotation.TargetApi; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Debug; +import android.os.ParcelFileDescriptor; +import android.provider.BaseColumns; +import android.provider.Telephony.Mms; +import android.provider.Telephony.MmsSms; +import android.provider.Telephony.Threads; +import android.test.AndroidTestCase; +import android.util.Log; -import com.android.emailcommon.provider.EmailContent; -import com.android.emailcommon.provider.EmailContent.Message; -import com.android.emailcommon.provider.EmailContent.MessageColumns; -import com.android.emailcommon.provider.EmailContent.SyncColumns; +import com.android.bluetooth.map.BluetoothMapMasInstance; +import com.android.bluetooth.map.BluetoothMapAccountItem; +import com.android.bluetooth.map.BluetoothMapAccountLoader; +import com.android.bluetooth.map.BluetoothMapAppParams; +import com.android.bluetooth.map.BluetoothMapContent; +import com.android.bluetooth.map.BluetoothMapFolderElement; +import com.android.bluetooth.map.BluetoothMapMessageListing; +import com.android.bluetooth.map.BluetoothMapUtils; +import com.android.bluetooth.map.BluetoothMapUtils.TYPE; +import com.android.bluetooth.map.MapContact; +import com.android.bluetooth.map.SmsMmsContacts; +import com.android.bluetooth.mapapi.BluetoothMapContract; +@TargetApi(19) public class BluetoothMapContentTest extends AndroidTestCase { + private static final String TAG = "BluetoothMapContentTest"; private static final boolean D = true; - private static final boolean V = true; private Context mContext; private ContentResolver mResolver; + private SmsMmsContacts mContacts = new SmsMmsContacts(); + + private BluetoothMapFolderElement mCurrentFolder; + private BluetoothMapAccountItem mAccount = null; + + private static final int MAS_ID = 0; + private static final int REMOTE_FEATURE_MASK = 0x07FFFFFF; + private static final BluetoothMapMasInstance mMasInstance = + new MockMasInstance(MAS_ID, REMOTE_FEATURE_MASK); + - static final String[] EMAIL_PROJECTION = new String[] { - EmailContent.RECORD_ID, - MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP, - MessageColumns.SUBJECT, MessageColumns.FLAG_READ, - MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS, - SyncColumns.SERVER_ID, MessageColumns.DRAFT_INFO, - MessageColumns.MESSAGE_ID, MessageColumns.MAILBOX_KEY, - MessageColumns.ACCOUNT_KEY, MessageColumns.FROM_LIST, - MessageColumns.TO_LIST, MessageColumns.CC_LIST, - MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST, - SyncColumns.SERVER_TIMESTAMP, MessageColumns.MEETING_INFO, - MessageColumns.SNIPPET, MessageColumns.PROTOCOL_SEARCH_INFO, - MessageColumns.THREAD_TOPIC + private Uri mEmailUri = null; + private Uri mEmailMessagesUri = null; + private Uri mEmailFolderUri = null; + private Uri mEmailAccountUri = null; + + static final String[] EMAIL_ACCOUNT_PROJECTION = new String[] { + BluetoothMapContract.MessageColumns.FOLDER_ID, + BluetoothMapContract.MessageColumns.ACCOUNT_ID, + }; + + private void printAccountInfo(Cursor c) { + if (D) Log.d(TAG, BluetoothMapContract.MessageColumns.ACCOUNT_ID + " : " + + c.getInt(c.getColumnIndex(BluetoothMapContract.MessageColumns.ACCOUNT_ID)) ); + } + + static final String[] BT_MESSAGE_ID_PROJECTION = new String[] { + BluetoothMapContract.MessageColumns._ID, + BluetoothMapContract.MessageColumns.DATE, }; + static final String[] BT_MESSAGE_PROJECTION = BluetoothMapContract.BT_MESSAGE_PROJECTION; + + static final String[] BT_ACCOUNT_PROJECTION = BluetoothMapContract.BT_ACCOUNT_PROJECTION; + + static final String[] BT_FOLDER_PROJECTION = BluetoothMapContract.BT_FOLDER_PROJECTION; + + BluetoothMapAccountLoader loader; + LinkedHashMap<BluetoothMapAccountItem, ArrayList<BluetoothMapAccountItem>> mFullList; + + public BluetoothMapContentTest() { + super(); + } + + private void initTestSetup(){ + mContext = this.getContext(); + mResolver = mContext.getContentResolver(); + + // find enabled account + loader = new BluetoothMapAccountLoader(mContext); + mFullList = loader.parsePackages(false); + String accountId = getEnabledAccount(); + Uri tmpEmailUri = Uri.parse("content://com.android.email.bluetoothprovider/"); + + mEmailUri = Uri.withAppendedPath(tmpEmailUri, accountId + "/"); + mEmailMessagesUri = Uri.parse(mEmailUri + BluetoothMapContract.TABLE_MESSAGE); + mEmailFolderUri = Uri.parse(mEmailUri + BluetoothMapContract.TABLE_FOLDER); + mEmailAccountUri = Uri.parse(tmpEmailUri + BluetoothMapContract.TABLE_ACCOUNT); + + buildFolderStructure(); + + } + + public String getEnabledAccount(){ + if(D)Log.d(TAG,"getEnabledAccountItems()\n"); + String account = null; + for(BluetoothMapAccountItem app:mFullList.keySet()){ + ArrayList<BluetoothMapAccountItem> accountList = mFullList.get(app); + for(BluetoothMapAccountItem acc: accountList){ + mAccount = acc; + account = acc.getId(); + break; + } + } + return account; + } + + private void buildFolderStructure(){ + mCurrentFolder = new BluetoothMapFolderElement("root", null); // This will be the root element + BluetoothMapFolderElement tmpFolder; + tmpFolder = mCurrentFolder.addFolder("telecom"); // root/telecom + tmpFolder = tmpFolder.addFolder("msg"); // root/telecom/msg + if(mEmailFolderUri != null) { + addEmailFolders(tmpFolder); + } + } + + private void addEmailFolders(BluetoothMapFolderElement parentFolder) { + BluetoothMapFolderElement newFolder; + String where = BluetoothMapContract.FolderColumns.PARENT_FOLDER_ID + + " = " + parentFolder.getFolderId(); + Cursor c = mContext.getContentResolver().query(mEmailFolderUri, + BluetoothMapContract.BT_FOLDER_PROJECTION, where, null, null); + if (c != null) { + c.moveToPosition(-1); + while (c.moveToNext()) { + String name = c.getString(c.getColumnIndex(BluetoothMapContract.FolderColumns.NAME)); + long id = c.getLong(c.getColumnIndex(BluetoothMapContract.FolderColumns._ID)); + newFolder = parentFolder.addEmailFolder(name, id); + addEmailFolders(newFolder); // Use recursion to add any sub folders + } + c.close(); + } else { + if (D) Log.d(TAG, "addEmailFolders(): no elements found"); + } + } + + private BluetoothMapFolderElement getInbox() { + BluetoothMapFolderElement tmpFolderElement = null; + + tmpFolderElement = mCurrentFolder.getSubFolder("telecom"); + tmpFolderElement = tmpFolderElement.getSubFolder("msg"); + tmpFolderElement = tmpFolderElement.getSubFolder("inbox"); + return tmpFolderElement; + } + + private BluetoothMapFolderElement getOutbox() { + BluetoothMapFolderElement tmpFolderElement = null; + + tmpFolderElement = mCurrentFolder.getSubFolder("telecom"); + tmpFolderElement = tmpFolderElement.getSubFolder("msg"); + tmpFolderElement = tmpFolderElement.getSubFolder("outbox"); + return tmpFolderElement; + } + + private String getDateTimeString(long timestamp) { SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); Date date = new Date(timestamp); return format.format(date); // Format to YYYYMMDDTHHMMSS local time } - private void printEmail(Cursor c) { - if (D) Log.d(TAG, "printEmail " + - c.getLong(c.getColumnIndex(EmailContent.RECORD_ID)) + - "\n " + MessageColumns.DISPLAY_NAME + " : " + c.getString(c.getColumnIndex(MessageColumns.DISPLAY_NAME)) + - "\n " + MessageColumns.TIMESTAMP + " : " + getDateTimeString(c.getLong(c.getColumnIndex(MessageColumns.TIMESTAMP))) + - "\n " + MessageColumns.SUBJECT + " : " + c.getString(c.getColumnIndex(MessageColumns.SUBJECT)) + - "\n " + MessageColumns.FLAG_READ + " : " + c.getString(c.getColumnIndex(MessageColumns.FLAG_READ)) + - "\n " + MessageColumns.FLAG_ATTACHMENT + " : " + c.getInt(c.getColumnIndex(MessageColumns.FLAG_ATTACHMENT)) + - "\n " + MessageColumns.FLAGS + " : " + c.getInt(c.getColumnIndex(MessageColumns.FLAGS)) + - "\n " + SyncColumns.SERVER_ID + " : " + c.getInt(c.getColumnIndex(SyncColumns.SERVER_ID)) + - "\n " + MessageColumns.DRAFT_INFO + " : " + c.getInt(c.getColumnIndex(MessageColumns.DRAFT_INFO)) + - "\n " + MessageColumns.MESSAGE_ID + " : " + c.getInt(c.getColumnIndex(MessageColumns.MESSAGE_ID)) + - "\n " + MessageColumns.MAILBOX_KEY + " : " + c.getInt(c.getColumnIndex(MessageColumns.MAILBOX_KEY)) + - "\n " + MessageColumns.ACCOUNT_KEY + " : " + c.getInt(c.getColumnIndex(MessageColumns.ACCOUNT_KEY)) + - "\n " + MessageColumns.FROM_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.FROM_LIST)) + - "\n " + MessageColumns.TO_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.TO_LIST)) + - "\n " + MessageColumns.CC_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.CC_LIST)) + - "\n " + MessageColumns.BCC_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.BCC_LIST)) + - "\n " + MessageColumns.REPLY_TO_LIST + " : " + c.getString(c.getColumnIndex(MessageColumns.REPLY_TO_LIST)) + - "\n " + SyncColumns.SERVER_TIMESTAMP + " : " + getDateTimeString(c.getLong(c.getColumnIndex(SyncColumns.SERVER_TIMESTAMP))) + - "\n " + MessageColumns.MEETING_INFO + " : " + c.getString(c.getColumnIndex(MessageColumns.MEETING_INFO)) + - "\n " + MessageColumns.SNIPPET + " : " + c.getString(c.getColumnIndex(MessageColumns.SNIPPET)) + - "\n " + MessageColumns.PROTOCOL_SEARCH_INFO + " : " + c.getString(c.getColumnIndex(MessageColumns.PROTOCOL_SEARCH_INFO)) + - "\n " + MessageColumns.THREAD_TOPIC + " : " + c.getString(c.getColumnIndex(MessageColumns.THREAD_TOPIC))); - } - - public void dumpEmailMessageTable() { - Log.d(TAG, "**** Dump of email message table ****"); - - Cursor c = mResolver.query(Message.CONTENT_URI, - EMAIL_PROJECTION, null, null, "_id DESC"); + private void printCursor(Cursor c) { + StringBuilder sb = new StringBuilder(); + sb.append("\nprintCursor:\n"); + for(int i = 0; i < c.getColumnCount(); i++) { + if(c.getColumnName(i).equals(BluetoothMapContract.MessageColumns.DATE)){ + sb.append(" ").append(c.getColumnName(i)) + .append(" : ").append(getDateTimeString(c.getLong(i))).append("\n"); + } else { + sb.append(" ").append(c.getColumnName(i)) + .append(" : ").append(c.getString(i)).append("\n"); + } + } + Log.d(TAG, sb.toString()); + } + + private void dumpMessageContent(Cursor c) { + long id = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns._ID)); + Uri uri = Uri.parse(mEmailMessagesUri + "/" + id + + "/" + BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS); + FileInputStream is = null; + ParcelFileDescriptor fd = null; + int count; + try { + fd = mResolver.openFileDescriptor(uri, "r"); + is = new FileInputStream(fd.getFileDescriptor()); + byte[] buffer = new byte[1024]; + + while((count = is.read(buffer)) != -1) { + Log.d(TAG, new String(buffer,0, count)); + } + + + } catch (FileNotFoundException e) { + Log.w(TAG, e); + } catch (IOException e) { + Log.w(TAG, e); + } + finally { + try { + if(is != null) + is.close(); + } catch (IOException e) {} + try { + if(fd != null) + fd.close(); + } catch (IOException e) {} + } + } + + /** + * Create a new message in the database outbox, based on the content of c. + * @param c + */ + private void writeMessageContent(Cursor c) { + long id = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns._ID)); + Uri uri = Uri.parse(mEmailMessagesUri + "/" + id + "/" + + BluetoothMapContract.FILE_MSG_NO_ATTACHMENTS); + FileInputStream is = null; + ParcelFileDescriptor fd = null; + FileOutputStream os = null; + ParcelFileDescriptor fdOut = null; + + ContentValues newMessage = new ContentValues(); + BluetoothMapFolderElement outFolder = getOutbox(); + newMessage.put(BluetoothMapContract.MessageColumns.FOLDER_ID, outFolder.getFolderId()); + // Now insert the empty message into outbox (Maybe it should be draft first, and then a move?) + // TODO: Examine if we need to set some additional flags, e.g. visable? + Uri uriOut = mResolver.insert(mEmailMessagesUri, newMessage); + int count; + try { + fd = mResolver.openFileDescriptor(uri, "r"); + is = new FileInputStream(fd.getFileDescriptor()); + fdOut = mResolver.openFileDescriptor(uri, "w"); + os = new FileOutputStream(fdOut.getFileDescriptor()); + byte[] buffer = new byte[1024]; + + while((count = is.read(buffer)) != -1) { + Log.d(TAG, new String(buffer,0, count)); + os.write(buffer, 0, count); + } + } catch (FileNotFoundException e) { + Log.w(TAG, e); + } catch (IOException e) { + Log.w(TAG, e); + } + finally { + try { + if(is != null) + is.close(); + } catch (IOException e) {} + try { + if(fd != null) + fd.close(); + } catch (IOException e) {} + try { + if(os != null) + os.close(); + } catch (IOException e) {} + try { + if(fdOut != null) + fdOut.close(); + } catch (IOException e) {} + } + } + + private void writeMessage(Cursor c) { + Log.d(TAG, "c.getCount() = " + c.getCount()); + c.moveToPosition(-1); + if (c.moveToNext()) { + writeMessageContent(c); + } + c.close(); + } + + + private void dumpCursor(Cursor c) { + Log.d(TAG, "c.getCount() = " + c.getCount()); + c.moveToPosition(-1); + while (c.moveToNext()) { + printCursor(c); + } + c.close(); + } + + private void callBluetoothProvider() { + Log.d(TAG, "**** Test call into email provider ****"); + int accountId = 0; + int mailboxId = 0; + + Log.d(TAG, "contentUri = " + mEmailMessagesUri); + + Cursor c = mResolver.query(mEmailMessagesUri, EMAIL_ACCOUNT_PROJECTION, + null, null, "_id DESC"); if (c != null) { Log.d(TAG, "c.getCount() = " + c.getCount()); c.moveToPosition(-1); while (c.moveToNext()) { - printEmail(c); + printAccountInfo(c); + mailboxId = c.getInt(c.getColumnIndex( + BluetoothMapContract.MessageColumns.FOLDER_ID)); + accountId = c.getInt(c.getColumnIndex( + BluetoothMapContract.MessageColumns.ACCOUNT_ID)); } + c.close(); } else { Log.d(TAG, "query failed"); - c.close(); } + + final Bundle extras = new Bundle(2); + /* TODO: find mailbox from DB */ + extras.putLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID, mailboxId); + extras.putLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, accountId); + Bundle myBundle = mResolver.call(mEmailUri, BluetoothMapContract.METHOD_UPDATE_FOLDER, + null, extras); } - public BluetoothMapContentTest() { - super(); + + public void testMsgListing() { + initTestSetup(); + BluetoothMapContent mBtMapContent = new BluetoothMapContent(mContext, mAccount, + mMasInstance); + BluetoothMapAppParams appParams = new BluetoothMapAppParams(); + Log.d(TAG, "**** testMsgListing **** "); + BluetoothMapFolderElement fe = getInbox(); + + if (fe != null) { + if (D) Log.d(TAG, "folder name=" + fe.getName()); + + appParams.setFilterMessageType(0x0B); + appParams.setMaxListCount(1024); + appParams.setStartOffset(0); + + BluetoothMapMessageListing msgListing = mBtMapContent.msgListing(fe, appParams); + int listCount = msgListing.getCount(); + int msgListingSize = mBtMapContent.msgListingSize(fe, appParams); + + if (listCount == msgListingSize) { + Log.d(TAG, "testMsgListing - " + listCount ); + } + else { + Log.d(TAG, "testMsgListing - None"); + } + } + else { + Log.d(TAG, "testMsgListing - failed "); + } + + } + + public void testMsgListingUnread() { + initTestSetup(); + BluetoothMapContent mBtMapContent = new BluetoothMapContent(mContext, mAccount, + mMasInstance); + BluetoothMapAppParams appParams = new BluetoothMapAppParams(); + Log.d(TAG, "**** testMsgListingUnread **** "); + BluetoothMapFolderElement fe = getInbox(); + + if (fe != null) { + + appParams.setFilterReadStatus(0x01); + appParams.setFilterMessageType(0x0B); + appParams.setMaxListCount(1024); + appParams.setStartOffset(0); + + BluetoothMapMessageListing msgListing = mBtMapContent.msgListing(fe, appParams); + + int listCount = msgListing.getCount(); + if (msgListing.getCount() > 0) { + Log.d(TAG, "testMsgListingUnread - " + listCount ); + } + else { + Log.d(TAG, "testMsgListingUnread - None"); + } + } + else { + Log.d(TAG, "testMsgListingUnread - getInbox failed "); + } + } + + public void testMsgListingWithOriginator() { + initTestSetup(); + BluetoothMapContent mBtMapContent = new BluetoothMapContent(mContext, mAccount, + mMasInstance); + BluetoothMapAppParams appParams = new BluetoothMapAppParams(); + Log.d(TAG, "**** testMsgListingUnread **** "); + BluetoothMapFolderElement fe = getInbox(); + + if (fe != null) { + + appParams.setFilterOriginator("*scsc.*"); + appParams.setFilterMessageType(0x0B); + appParams.setMaxListCount(1024); + appParams.setStartOffset(0); + + BluetoothMapMessageListing msgListing = mBtMapContent.msgListing(fe, appParams); + + int listCount = msgListing.getCount(); + if (msgListing.getCount() > 0) { + Log.d(TAG, "testMsgListingWithOriginator - " + listCount ); + } + else { + Log.d(TAG, "testMsgListingWithOriginator - None"); + } + } else { + Log.d(TAG, "testMsgListingWithOriginator - getInbox failed "); + } + } + + public void testGetMessages() { + initTestSetup(); + BluetoothMapContent mBtMapContent = new BluetoothMapContent(mContext, mAccount, + mMasInstance); + BluetoothMapAppParams appParams = new BluetoothMapAppParams(); + Log.d(TAG, "**** testGetMessages **** "); + BluetoothMapFolderElement fe = getInbox(); + + if (fe != null) { + appParams.setAttachment(0); + appParams.setCharset(BluetoothMapContent.MAP_MESSAGE_CHARSET_UTF8); + + //get message handles + Cursor c = mResolver.query(mEmailMessagesUri, BT_MESSAGE_ID_PROJECTION, + null, null, "_id DESC"); + if (c != null) { + c.moveToPosition(-1); + while (c.moveToNext()) { + Long id = c.getLong(c.getColumnIndex(BluetoothMapContract.MessageColumns._ID)); + String handle = BluetoothMapUtils.getMapHandle(id, TYPE.EMAIL); + try { + // getMessage + byte[] bytes = mBtMapContent.getMessage(handle, appParams, fe, "1.1"); + Log.d(TAG, "testGetMessages id=" + id + ", handle=" + handle + + ", length=" + bytes.length ); + String testPrint = new String(bytes); + Log.d(TAG, "testGetMessage (only dump first part):\n" + testPrint ); + } catch (UnsupportedEncodingException e) { + Log.w(TAG, e); + } finally { + + } + } + } else { + Log.d(TAG, "testGetMessages - no cursor "); + } + } else { + Log.d(TAG, "testGetMessages - getInbox failed "); + } + + } + + public void testDumpAccounts() { + initTestSetup(); + Log.d(TAG, "**** testDumpAccounts **** \n from: " + mEmailAccountUri.toString()); + Cursor c = mResolver.query(mEmailAccountUri, BT_ACCOUNT_PROJECTION, null, null, "_id DESC"); + if (c != null) { + dumpCursor(c); + } else { + Log.d(TAG, "query failed"); + } + Log.w(TAG, "testDumpAccounts(): ThreadId: " + Thread.currentThread().getId()); + + } + + public void testAccountUpdate() { + initTestSetup(); + Log.d(TAG, "**** testAccountUpdate **** \n of: " + mEmailAccountUri.toString()); + Cursor c = mResolver.query(mEmailAccountUri, BT_ACCOUNT_PROJECTION, null, null, "_id DESC"); + + if (c != null) { + c.moveToPosition(-1); + while (c.moveToNext()) { + printCursor(c); + Long id = c.getLong(c.getColumnIndex(BluetoothMapContract.AccountColumns._ID)); + int exposeFlag = c.getInt( + c.getColumnIndex(BluetoothMapContract.AccountColumns.FLAG_EXPOSE)); + String where = BluetoothMapContract.AccountColumns._ID + " = " + id; + ContentValues values = new ContentValues(); + if(exposeFlag == 1) { + values.put(BluetoothMapContract.AccountColumns.FLAG_EXPOSE, (int) 0); + } else { + values.put(BluetoothMapContract.AccountColumns.FLAG_EXPOSE, (int) 1); + } + Log.i(TAG, "Calling update() with selection: " + where + + "values(exposeFlag): " + + values.getAsInteger(BluetoothMapContract.AccountColumns.FLAG_EXPOSE)); + mResolver.update(mEmailAccountUri, values, where, null); + } + c.close(); + } + } public void testDumpMessages() { + initTestSetup(); + + if (D) Log.d(TAG, "**** testDumpMessages **** \n uri=" + mEmailMessagesUri.toString()); + BluetoothMapFolderElement fe = getInbox(); + if (fe != null) + { + String where =""; + //where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + fe.getEmailFolderId(); + Cursor c = mResolver.query(mEmailMessagesUri, BT_MESSAGE_PROJECTION, + where, null, "_id DESC"); + if (c != null) { + dumpCursor(c); + } else { + if (D) Log.d(TAG, "query failed"); + } + if (D) Log.w(TAG, "dumpMessage(): ThreadId: " + Thread.currentThread().getId()); + } else { + if (D) Log.w(TAG, "dumpMessage(): ThreadId: " + Thread.currentThread().getId()); + } + } + + public void testDumpMessageContent() { + initTestSetup(); + + Log.d(TAG, "**** testDumpMessageContent **** from: " + mEmailMessagesUri.toString()); +// BluetoothMapFolderElement fe = getInbox(); +// String where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + fe.getEmailFolderId(); +// where += " AND " + BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY + " = 0"; + + Cursor c = mResolver.query(mEmailMessagesUri, BT_MESSAGE_PROJECTION, null, null, "_id DESC"); + if (c != null && c.moveToNext()) { + dumpMessageContent(c); + } else { + Log.d(TAG, "query failed"); + } + Log.w(TAG, "dumpMessage(): ThreadId: " + Thread.currentThread().getId()); + } + + public void testWriteMessageContent() { + initTestSetup(); + Log.d(TAG, "**** testWriteMessageContent **** from: " + mEmailMessagesUri.toString()); + BluetoothMapFolderElement fe = getInbox(); + String where = BluetoothMapContract.MessageColumns.FOLDER_ID + " = " + fe.getFolderId(); +// where += " AND " + BluetoothMapContract.MessageColumns.HIGH_PRIORITY + " = 0"; + Cursor c = mResolver.query(mEmailMessagesUri, BT_MESSAGE_PROJECTION, where, null, "_id DESC"); + if (c != null) { + writeMessage(c); + } else { + Log.d(TAG, "query failed"); + } + Log.w(TAG, "writeMessage(): ThreadId: " + Thread.currentThread().getId()); + } + + /* + * Handle test cases + */ + private static final long HANDLE_TYPE_SMS_CDMA_MASK = (((long)0x1)<<60); + + public void testHandle() { + String handleStr = null; + Debug.startMethodTracing("str_format"); + for(long i = 0; i < 10000; i++) { + handleStr = String.format("%016X",(i | HANDLE_TYPE_SMS_CDMA_MASK)); + } + Debug.stopMethodTracing(); + Debug.startMethodTracing("getHandleString"); + for(long i = 0; i < 10000; i++) { + handleStr = BluetoothMapUtils.getLongAsString(i | HANDLE_TYPE_SMS_CDMA_MASK); + } + Debug.stopMethodTracing(); + } + + /* + * Folder test cases + */ + + public void testDumpEmailFolders() { + initTestSetup(); + Debug.startMethodTracing(); + String where = null; + Cursor c = mResolver.query(mEmailFolderUri, BT_FOLDER_PROJECTION, where, null, "_id DESC"); + if (c != null) { + dumpCursor(c); + c.close(); + } else { + Log.d(TAG, "query failed"); + } + Debug.stopMethodTracing(); + } + + public void testFolderPath() { + initTestSetup(); + Log.d(TAG, "**** testFolderPath **** "); + BluetoothMapFolderElement fe = getInbox(); + BluetoothMapFolderElement folder = fe.getFolderById(fe.getFolderId()); + if(folder == null) { + Log.d(TAG, "**** testFolderPath unable to find the folder with id: " + + fe.getFolderId()); + } + else { + Log.d(TAG, "**** testFolderPath found the folder with id: " + + fe.getFolderId() + "\nFull path: " + + folder.getFullPath()); + } + } + + public void testFolderElement() { + Log.d(TAG, "**** testFolderElement **** "); + BluetoothMapFolderElement fe = new BluetoothMapFolderElement("root", null); + fe = fe.addEmailFolder("MsG", 1); + fe.addEmailFolder("Outbox", 100); + fe.addEmailFolder("Sent", 200); + BluetoothMapFolderElement inbox = fe.addEmailFolder("Inbox", 300); + fe.addEmailFolder("Draft", 400); + fe.addEmailFolder("Deleted", 500); + inbox.addEmailFolder("keep", 301); + inbox.addEmailFolder("private", 302); + inbox.addEmailFolder("junk", 303); + + BluetoothMapFolderElement folder = fe.getFolderById(400); + assertEquals("draft", folder.getName()); + assertEquals("private", fe.getFolderById(302).getName()); + assertEquals("junk", fe.getRoot().getFolderById(303).getName()); + assertEquals("msg/inbox/keep", fe.getFolderById(301).getFullPath()); + } + + /* + * SMS test cases + */ + public void testAddSmsEntries() { + int count = 1000; + mContext = this.getContext(); + mResolver = mContext.getContentResolver(); + ContentValues values[] = new ContentValues[count]; + long date = System.currentTimeMillis(); + Log.i(TAG, "Preparing messages..."); + for (int x=0;x<count;x++){ + //if (D) Log.d(TAG, "*** Adding dummy sms #"+x); + + ContentValues item = new ContentValues(4); + item.put("address", "1234"); + item.put("body", "test message "+x); + item.put("date", date); + item.put("read", "0"); + + values[x] = item; + // Uri mUri = mResolver.insert(Uri.parse("content://sms"), item); + } + Log.i(TAG, "Starting bulk insert..."); + mResolver.bulkInsert(Uri.parse("content://sms"), values); + Log.i(TAG, "Bulk insert done."); + } + + public void testAddSms() { + mContext = this.getContext(); + mResolver = mContext.getContentResolver(); + if (D) Log.d(TAG, "*** Adding dummy sms #"); + + ContentValues item = new ContentValues(); + item.put("address", "1234"); + item.put("body", "test message"); + item.put("date", System.currentTimeMillis()); + item.put("read", "0"); + + Uri mUri = mResolver.insert(Uri.parse("content://sms"), item); + } + + public void testServiceSms() { mContext = this.getContext(); mResolver = mContext.getContentResolver(); - dumpEmailMessageTable(); + if (D) Log.d(TAG, "*** Adding dummy sms #"); + + ContentValues item = new ContentValues(); + item.put("address", "C-Bonde"); + item.put("body", "test message"); + item.put("date", System.currentTimeMillis()); + item.put("read", "0"); + + Uri mUri = mResolver.insert(Uri.parse("content://sms"), item); + } + + /* + * MMS content test cases + */ + public static final int MMS_FROM = 0x89; + public static final int MMS_TO = 0x97; + public static final int MMS_BCC = 0x81; + public static final int MMS_CC = 0x82; + + private void printMmsAddr(long id) { + final String[] projection = null; + String selection = new String("msg_id=" + id); + String uriStr = String.format("content://mms/%d/addr", id); + Uri uriAddress = Uri.parse(uriStr); + Cursor c = mResolver.query(uriAddress, projection, selection, null, null); + + if (c.moveToFirst()) { + do { + String add = c.getString(c.getColumnIndex("address")); + Integer type = c.getInt(c.getColumnIndex("type")); + if (type == MMS_TO) { + if (D) Log.d(TAG, " recipient: " + add + " (type: " + type + ")"); + } else if (type == MMS_FROM) { + if (D) Log.d(TAG, " originator: " + add + " (type: " + type + ")"); + } else { + if (D) Log.d(TAG, " address other: " + add + " (type: " + type + ")"); + } + printCursor(c); + + } while(c.moveToNext()); + } + } + + private void printMmsPartImage(long partid) { + String uriStr = String.format("content://mms/part/%d", partid); + Uri uriAddress = Uri.parse(uriStr); + int ch; + StringBuffer sb = new StringBuffer(""); + InputStream is = null; + + try { + is = mResolver.openInputStream(uriAddress); + + while ((ch = is.read()) != -1) { + sb.append((char)ch); + } + if (D) Log.d(TAG, sb.toString()); + + } catch (IOException e) { + // do nothing for now + e.printStackTrace(); + } + } + + private void printMmsParts(long id) { + final String[] projection = null; + String selection = new String("mid=" + id); + String uriStr = String.format("content://mms/%d/part", id); + Uri uriAddress = Uri.parse(uriStr); + Cursor c = mResolver.query(uriAddress, projection, selection, null, null); + + if (c.moveToFirst()) { + int i = 0; + do { + if (D) Log.d(TAG, " part " + i++); + printCursor(c); + + /* if (ct.equals("image/jpeg")) { */ + /* printMmsPartImage(partid); */ + /* } */ + } while(c.moveToNext()); + } } + + public void dumpMmsTable() { + mContext = this.getContext(); + mResolver = mContext.getContentResolver(); + + if (D) Log.d(TAG, "**** Dump of mms table ****"); + Cursor c = mResolver.query(Mms.CONTENT_URI, + null, null, null, "_id DESC"); + if (c != null) { + if (D) Log.d(TAG, "c.getCount() = " + c.getCount()); + c.moveToPosition(-1); + while (c.moveToNext()) { + Log.d(TAG,"Message:"); + printCursor(c); + long id = c.getLong(c.getColumnIndex(BaseColumns._ID)); + Log.d(TAG,"Address:"); + printMmsAddr(id); + Log.d(TAG,"Parts:"); + printMmsParts(id); + } + c.close(); + } else { + Log.d(TAG, "query failed"); + } + } + + /** + * This dumps the thread database. + * Interesting how useful this is. + * - DATE is described to be the creation date of the thread. But it actually + * contains the time-date of the last activity of the thread. + * - RECIPIENTS is a list of the contacts related to the thread. The number can + * be found for both MMS and SMS in the "canonical-addresses" table. + * - The READ column tells if the thread have been read. (read = 1: no unread messages) + * - The snippet is a small piece of text from the last message, and could be used as thread + * name. Please however note that if we do this, the version-counter should change each + * time a message is added to the thread. But since it changes the read attribute and + * last activity, it changes anyway. + * - + */ + + + public void dumpThreadsTable() { + mContext = this.getContext(); + mResolver = mContext.getContentResolver(); + mContacts.clearCache(); + Uri uri = Threads.CONTENT_URI.buildUpon().appendQueryParameter("simple", "true").build(); + + if (D) Log.d(TAG, "**** Dump of Threads table ****\nUri: " + uri); + Cursor c = mResolver.query(uri, + null, null, null, "_id DESC"); + if (c != null) { + if (D) Log.d(TAG, "c.getCount() = " + c.getCount()); + c.moveToPosition(-1); + while (c.moveToNext()) { + Log.d(TAG,"Threads:"); + printCursor(c); + String ids = c.getString(c.getColumnIndex(Threads.RECIPIENT_IDS)); + Log.d(TAG,"Address:"); + printAddresses(ids); +/* Log.d(TAG,"Parts:"); + printMmsParts(id);*/ + } + c.close(); + } else { + Log.d(TAG, "query failed"); + } + } + + /** + * This test shows the content of the canonicalAddresses table. + * Conclusion: + * The _id column matches the id's from the RECIPIENT_IDS column + * in the Threads table, hence are to be used to map from an id to + * a phone number, which then can be matched to a contact. + */ + public void dumpCanAddrTable() { + mContext = this.getContext(); + mResolver = mContext.getContentResolver(); + Uri uri = Uri.parse("content://mms-sms/canonical-addresses"); + uri = MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build(); + dumpUri(uri); + } + + public void dumpUri(Uri uri) { + if (D) Log.d(TAG, "**** Dump of table ****\nUri: " + uri); + Cursor c = mResolver.query(uri, null, null, null, null); + if (c != null) { + if (D) Log.d(TAG, "c.getCount() = " + c.getCount()); + c.moveToPosition(-1); + while (c.moveToNext()) { + Log.d(TAG,"Entry: " + c.getPosition()); + printCursor(c); + } + c.close(); + } else { + Log.d(TAG, "query failed"); + } + } + + private void printAddresses(String idsStr) { + String[] ids = idsStr.split(" "); + for (String id : ids) { + long longId; + try { + longId = Long.parseLong(id); + String addr = mContacts.getPhoneNumber(mResolver, longId); + MapContact contact = mContacts.getContactNameFromPhone(addr, mResolver); + Log.d(TAG, " id " + id + ": " + addr + " - " + contact.getName() + + " X-BT-UID: " + contact.getXBtUidString()); + } catch (NumberFormatException ex) { + // skip this id + continue; + } + } + } + } diff --git a/tests/src/com/android/bluetooth/tests/BluetoothMapIMContentTest.java b/tests/src/com/android/bluetooth/tests/BluetoothMapIMContentTest.java new file mode 100644 index 000000000..2d100e4a7 --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/BluetoothMapIMContentTest.java @@ -0,0 +1,171 @@ +package com.android.bluetooth.tests; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.BaseColumns; +import android.test.AndroidTestCase; +import android.util.Log; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import com.android.bluetooth.mapapi.BluetoothMapContract; +import com.android.bluetooth.mapapi.BluetoothMapContract.ConversationColumns; + +//import info.guardianproject.otr.app.im.provider.Imps; +//import info.guardianproject.otr.app.im.provider.ImpsBluetoothProvider; + +public class BluetoothMapIMContentTest extends AndroidTestCase { + private static final String TAG = "BluetoothMapIMContentTest"; + + private static final boolean D = true; + private static final boolean V = true; + + private Context mContext; + private ContentResolver mResolver; + + private String getDateTimeString(long timestamp) { + SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); + Date date = new Date(timestamp); + return format.format(date); // Format to YYYYMMDDTHHMMSS local time + } + + private void printCursor(Cursor c) { + StringBuilder sb = new StringBuilder(); + sb.append("\nprintCursor:\n"); + for(int i = 0; i < c.getColumnCount(); i++) { + if(c.getColumnName(i).equals(BluetoothMapContract.MessageColumns.DATE) || + c.getColumnName(i).equals(BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY) || + c.getColumnName(i).equals(BluetoothMapContract.ChatStatusColumns.LAST_ACTIVE) || + c.getColumnName(i).equals(BluetoothMapContract.PresenceColumns.LAST_ONLINE) ){ + sb.append(" ").append(c.getColumnName(i)).append(" : ").append(getDateTimeString(c.getLong(i))).append("\n"); + } else { + sb.append(" ").append(c.getColumnName(i)).append(" : ").append(c.getString(i)).append("\n"); + } + } + Log.d(TAG, sb.toString()); + } + + private void dumpImMessageTable() { + Log.d(TAG, "**** Dump of im message table ****"); + + Cursor c = mResolver.query( + BluetoothMapContract.buildMessageUri("info.guardianproject.otr.app.im.provider.bluetoothprovider"), + BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, null, null, "_id DESC"); + if (c != null) { + Log.d(TAG, "c.getCount() = " + c.getCount()); + c.moveToPosition(-1); + while (c.moveToNext()) { + printCursor(c); + } + } else { + Log.d(TAG, "query failed"); + c.close(); + } + + } + + private void insertImMessage( ) { + Log.d(TAG, "**** Insert message in im message table ****"); + ContentValues cv = new ContentValues(); + cv.put(BluetoothMapContract.MessageColumns.BODY, "This is a test to insert a message"); + cv.put(BluetoothMapContract.MessageColumns.DATE, System.currentTimeMillis()); + cv.put(BluetoothMapContract.MessageColumns.THREAD_ID, 2); + Uri uri = BluetoothMapContract.buildMessageUri("info.guardianproject.otr.app.im.provider.bluetoothprovider"); + Uri uriWithId = mResolver.insert(uri, cv); + if (uriWithId != null) { + Log.d(TAG, "uriWithId = " + uriWithId.toString()); + } else { + Log.d(TAG, "query failed"); + } + + } + + private void dumpImConversationTable() { + Log.d(TAG, "**** Dump of conversation message table ****"); + + Uri uri = BluetoothMapContract.buildConversationUri( + "info.guardianproject.otr.app.im.provider.bluetoothprovider", "1"); + uri = uri.buildUpon().appendQueryParameter(BluetoothMapContract.FILTER_ORIGINATOR_SUBSTRING, + "asp").build(); + + Cursor convo = mResolver.query( + uri, + BluetoothMapContract.BT_CONVERSATION_PROJECTION, null, null, + null); + + if (convo != null) { + Log.d(TAG, "c.getCount() = " + convo.getCount()); + + while(convo.moveToNext()) { + printCursor(convo); + } + convo.close(); + } else { + Log.d(TAG, "query failed"); + } + } + + + private void dumpImContactsTable() { + Log.d(TAG, "**** Dump of contacts message table ****"); + Cursor cContact = mResolver.query( + BluetoothMapContract.buildConvoContactsUri("info.guardianproject.otr.app.im.provider.bluetoothprovider","1"), + BluetoothMapContract.BT_CONTACT_CHATSTATE_PRESENCE_PROJECTION, null, null, "_id DESC"); + + if (cContact != null && cContact.moveToFirst()) { + Log.d(TAG, "c.getCount() = " + cContact.getCount()); + do { + printCursor(cContact); + } while(cContact.moveToNext()); + + } else { + Log.d(TAG, "query failed"); + cContact.close(); + } + } + + private void dumpImAccountsTable() { + Log.d(TAG, "**** Dump of accounts table ****"); + Cursor cContact = mResolver.query( + BluetoothMapContract.buildAccountUri("info.guardianproject.otr.app.im.provider.bluetoothprovider"), + BluetoothMapContract.BT_ACCOUNT_PROJECTION, null, null, "_id DESC"); + + if (cContact != null && cContact.moveToFirst()) { + Log.d(TAG, "c.getCount() = " + cContact.getCount()); + do { + printCursor(cContact); + } while(cContact.moveToNext()); + + } else { + Log.d(TAG, "query failed"); + cContact.close(); + } + } + + + public BluetoothMapIMContentTest() { + super(); + } + + public void testDumpMessages() { + mContext = this.getContext(); + mResolver = mContext.getContentResolver(); + dumpImMessageTable(); + dumpImConversationTable(); + dumpImContactsTable(); + dumpImAccountsTable(); + + insertImMessage(); + + } + + public void testDumpConversations() { + mContext = this.getContext(); + mResolver = mContext.getContentResolver(); + dumpImConversationTable(); + } +}
\ No newline at end of file diff --git a/tests/src/com/android/bluetooth/tests/BluetoothMapUtilsTest.java b/tests/src/com/android/bluetooth/tests/BluetoothMapUtilsTest.java new file mode 100644 index 000000000..788b36f2a --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/BluetoothMapUtilsTest.java @@ -0,0 +1,116 @@ + +package com.android.bluetooth.tests; + +import android.test.AndroidTestCase; +import android.util.Log; +import android.util.Base64; + +import java.io.UnsupportedEncodingException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import com.android.bluetooth.SignedLongLong; +import com.android.bluetooth.map.BluetoothMapUtils; + +public class BluetoothMapUtilsTest extends AndroidTestCase { + private static final String TAG = "BluetoothMapUtilsTest"; + + private static final boolean D = true; + private static final boolean V = true; + private static final String encText1 = "=?UTF-8?b?w6bDuMOlw4bDmMOF?="; //æøåÆØÅ base64 + private static final String encText2 = "=?UTF-8?B?w6bDuMOlw4bDmMOF?="; //æøåÆØÅ base64 + private static final String encText3 = "=?UTF-8?q?=C3=A6=C3=B8=C3=A5=C3=86=C3=98=C3=85?="; //æøåÆØÅ QP + private static final String encText4 = "=?UTF-8?Q?=C3=A6=C3=B8=C3=A5=C3=86=C3=98=C3=85?="; //æøåÆØÅ QP + private static final String encText5 = "=?UTF-8?B?=C3=A6=C3=B8=C3=A5=C3=86=C3=98=C3=85?="; //QP in base64 string - should not compute + private static final String encText6 = "=?UTF-8?Q?w6bDuMOlw4bDmMOF?="; //æøåÆØÅ base64 in QP stirng - should not compute + private static final String encText7 = "this is a split =?UTF-8?Q?=C3=A6=C3=B8=C3=A5 ###123?= with more =?UTF-8?Q?=C3=A6=C3=B8=C3=A5 ###123?= inside"; // mix of QP and normal + private static final String encText8 = "this is a split =?UTF-8?B?w6bDuMOlICMjIzEyMw==?= with more =?UTF-8?Q?=C3=A6=C3=B8=C3=A5 ###123?= inside"; // mix of normal, QP and Base64 + private static final String encText9 = "=?UTF-8?Q??="; + private static final String encText10 = "=?UTF-8?Q??="; + private static final String encText11 = "=?UTF-8?Q??="; + + private static final String decText1 = "æøåÆØÅ"; + private static final String decText2 = "æøåÆØÅ"; + private static final String decText3 = "æøåÆØÅ"; + private static final String decText4 = "æøåÆØÅ"; + private static final String decText5 = encText5; + private static final String decText6 = "w6bDuMOlw4bDmMOF"; + private static final String decText7 = "this is a split æøå ###123 with more æøå ###123 inside"; + private static final String decText8 = "this is a split æøå ###123 with more æøå ###123 inside"; + + public BluetoothMapUtilsTest() { + super(); + + } + + + public void testEncoder(){ + assertTrue(BluetoothMapUtils.stripEncoding(encText1).equals(decText1)); + assertTrue(BluetoothMapUtils.stripEncoding(encText2).equals(decText2)); + assertTrue(BluetoothMapUtils.stripEncoding(encText3).equals(decText3)); + assertTrue(BluetoothMapUtils.stripEncoding(encText4).equals(decText4)); + assertTrue(BluetoothMapUtils.stripEncoding(encText5).equals(decText5)); + assertTrue(BluetoothMapUtils.stripEncoding(encText6).equals(decText6)); + Log.i(TAG,"##############################enc7:" + + BluetoothMapUtils.stripEncoding(encText7)); + assertTrue(BluetoothMapUtils.stripEncoding(encText7).equals(decText7)); + assertTrue(BluetoothMapUtils.stripEncoding(encText8).equals(decText8)); + } + + public void testXBtUid() throws UnsupportedEncodingException { + { + SignedLongLong expected = new SignedLongLong(0x12345678L, 0x90abcdefL); + /* this will cause an exception, since the value is too big... */ + SignedLongLong value; + value = SignedLongLong.fromString("90abcdef0000000012345678"); + assertTrue("expected: " + expected + " value = " + value, + 0 == value.compareTo(expected)); + assertEquals("expected: " + expected + " value = " + value, + expected.toHexString(), value.toHexString()); + Log.i(TAG,"Succesfully compared : " + value); + } + { + SignedLongLong expected = new SignedLongLong(0x12345678L, 0xfedcba9890abcdefL); + /* this will cause an exception, since the value is too big... */ + SignedLongLong value; + value = SignedLongLong.fromString("fedcba9890abcdef0000000012345678"); + assertTrue("expected: " + expected + " value = " + value, + 0 == value.compareTo(expected)); + assertEquals("expected: " + expected + " value = " + value, + expected.toHexString(), value.toHexString()); + Log.i(TAG,"Succesfully compared : " + value); + } + { + SignedLongLong expected = new SignedLongLong(0x12345678L, 0); + SignedLongLong value = SignedLongLong.fromString("000012345678"); + assertTrue("expected: " + expected + " value = " + value, + 0 == value.compareTo(expected)); + assertEquals("expected: " + expected + " value = " + value, + expected.toHexString(), value.toHexString()); + Log.i(TAG,"Succesfully compared : " + value); + } + { + SignedLongLong expected = new SignedLongLong(0x12345678L, 0); + SignedLongLong value = SignedLongLong.fromString("12345678"); + assertTrue("expected: " + expected + " value = " + value, + 0 == value.compareTo(expected)); + assertEquals("expected: " + expected + " value = " + value, + expected.toHexString(), value.toHexString()); + Log.i(TAG,"Succesfully compared : " + value); + } + { + SignedLongLong expected = new SignedLongLong(0x123456789abcdef1L, 0x9L); + SignedLongLong value = SignedLongLong.fromString("0009123456789abcdef1"); + assertTrue("expected: " + expected + " value = " + value, + 0 == value.compareTo(expected)); + assertEquals("expected: " + expected + " value = " + value, + expected.toHexString(), value.toHexString()); + Log.i(TAG,"Succesfully compared : " + value); + } + { + long expected = 0x123456789abcdefL; + long value = BluetoothMapUtils.getLongFromString(" 1234 5678 9abc-def"); + assertTrue("expected: " + expected + " value = " + value, value == expected); + } + } +} diff --git a/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java b/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java index 1fcec4e66..8724e9fb0 100755 --- a/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java +++ b/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java @@ -8,22 +8,19 @@ import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; +import org.apache.http.message.BasicHeaderElement; import org.apache.http.message.BasicHeaderValueFormatter; -import android.preference.PreferenceFragment; import android.test.AndroidTestCase; import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; import com.android.bluetooth.map.BluetoothMapAppParams; -import com.android.bluetooth.map.BluetoothMapUtils.TYPE; import com.android.bluetooth.map.BluetoothMapSmsPdu; +import com.android.bluetooth.map.BluetoothMapUtils; +import com.android.bluetooth.map.BluetoothMapUtils.TYPE; import com.android.bluetooth.map.BluetoothMapbMessage; -import com.android.bluetooth.map.BluetoothMapbMessageMms; +import com.android.bluetooth.map.BluetoothMapbMessageMime; import com.android.bluetooth.map.BluetoothMapbMessageSms; -import org.apache.http.message.BasicHeaderValueFormatter; -import org.apache.http.message.BasicHeaderElement; /*** * @@ -58,6 +55,15 @@ public class BluetoothMapbMessageTest extends AndroidTestCase { "EMAIL:casper@email.add\r\n" + "EMAIL:bonde@email.add\r\n" + "END:VCARD\r\n" + + "BEGIN:VCARD\r\n" + + "VERSION:3.0\r\n" + + "FN:Casper Bonde\r\n" + + "N:Bonde,Casper\r\n" + + "TEL:+4512345678\r\n" + + "TEL:+4587654321\r\n" + + "EMAIL:casper@email.add\r\n" + + "EMAIL:bonde@email.add\r\n" + + "END:VCARD\r\n" + "BEGIN:BENV\r\n" + "BEGIN:VCARD\r\n" + "VERSION:3.0\r\n" + @@ -68,6 +74,15 @@ public class BluetoothMapbMessageTest extends AndroidTestCase { "EMAIL:casper@email.add\r\n" + "EMAIL:bonde@email.add\r\n" + "END:VCARD\r\n" + + "BEGIN:VCARD\r\n" + + "VERSION:3.0\r\n" + + "FN:Jens Hansen\r\n" + + "N:\r\n" + + "TEL:+4512345678\r\n" + + "TEL:+4587654321\r\n" + + "EMAIL:casper@email.add\r\n" + + "EMAIL:bonde@email.add\r\n" + + "END:VCARD\r\n" + "BEGIN:BBODY\r\n" + "CHARSET:UTF-8\r\n" + "LENGTH:45\r\n" + @@ -81,8 +96,10 @@ public class BluetoothMapbMessageTest extends AndroidTestCase { String encoded; String[] phone = {"+4512345678", "+4587654321"}; String[] email = {"casper@email.add", "bonde@email.add"}; - msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email); - msg.addRecipient("", "Jens Hansen", phone, email); + msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null); + msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null); + msg.addRecipient("", "Jens Hansen", phone, email, null, null); + msg.addRecipient("", "Jens Hansen", phone, email, null, null); msg.setFolder("inbox"); msg.setSmsBody("This is a short message"); msg.setStatus(false); @@ -182,8 +199,8 @@ public class BluetoothMapbMessageTest extends AndroidTestCase { String encoded; String[] phone = {"00498912345678", "+4587654321"}; String[] email = {"casper@email.add", "bonde@email.add"}; - msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email); - msg.addRecipient("", "Jens Hansen", phone, email); + msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null); + msg.addRecipient("", "Jens Hansen", phone, email, null, null); msg.setFolder("inbox"); /* TODO: extract current time, and build the expected string */ msg.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus("Let's go fishing!", "00498912345678", date.getTime())); @@ -252,8 +269,8 @@ public class BluetoothMapbMessageTest extends AndroidTestCase { String encoded; String[] phone = {"00498912345678", "+4587654321"}; String[] email = {"casper@email.add", "bonde@email.add"}; - msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email); - msg.addRecipient("", "Jens Hansen", phone, email); + msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null); + msg.addRecipient("", "Jens Hansen", phone, email, null, null); msg.setFolder("outbox"); /* TODO: extract current time, and build the expected string */ msg.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus("Let's go fishing!", "00498912345678")); @@ -421,7 +438,7 @@ public class BluetoothMapbMessageTest extends AndroidTestCase { * Test encoding of a simple MMS text message (UTF8). This validates most parameters. */ public void testMmsEncodeText() { - BluetoothMapbMessageMms msg = new BluetoothMapbMessageMms(); + BluetoothMapbMessageMime msg = new BluetoothMapbMessageMime (); String str1 = "BEGIN:BMSG\r\n" + "VERSION:1.0\r\n" + @@ -449,9 +466,16 @@ public class BluetoothMapbMessageTest extends AndroidTestCase { "END:VCARD\r\n" + "BEGIN:BBODY\r\n" + "CHARSET:UTF-8\r\n" + - "LENGTH:45\r\n" + + "LENGTH:184\r\n" + "BEGIN:MSG\r\n" + - "This is a short message\r\n" + + "From: \"Jørn Hansen\" <bonde@email.add>;\r\n" + + "To: \"Jørn Hansen\" <bonde@email.add>;\r\n" + + "Cc: Jens Hansen <bonde@email.add>;\r\n" + + "\r\n" + + "This is a short message\r\n" + + "\r\n" + + "<partNameimage>\r\n" + + "\r\n" + "END:MSG\r\n" + "END:BBODY\r\n" + "END:BENV\r\n" + @@ -460,14 +484,14 @@ public class BluetoothMapbMessageTest extends AndroidTestCase { String encoded; String[] phone = {"+4512345678", "+4587654321"}; String[] email = {"casper@email.add", "bonde@email.add"}; - msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email); - msg.addRecipient("", "Jørn Hansen", phone, email); + msg.addOriginator("Bonde,Casper", "Casper Bonde", phone, email, null, null); + msg.addRecipient("", "Jørn Hansen", phone, email, null, null); msg.setFolder("inbox"); msg.setIncludeAttachments(false); msg.addTo("Jørn Hansen", "bonde@email.add"); msg.addCc("Jens Hansen", "bonde@email.add"); msg.addFrom("Jørn Hansen", "bonde@email.add"); - BluetoothMapbMessageMms.MimePart part = msg.addMimePart(); + BluetoothMapbMessageMime .MimePart part = msg.addMimePart(); part.mPartName = "partNameText"; part.mContentType ="dsfajfdlk/text/asdfafda"; try { @@ -485,6 +509,8 @@ public class BluetoothMapbMessageTest extends AndroidTestCase { msg.setStatus(false); msg.setType(TYPE.MMS); + msg.updateCharset(); + try { encoded = new String(msg.encode()); if(D) Log.d(TAG, encoded); @@ -501,5 +527,56 @@ public class BluetoothMapbMessageTest extends AndroidTestCase { if(D) Log.i(TAG, "The encoded header: " + headerStr); } + public void testQuotedPrintable() { + testQuotedPrintableIso8859_1(); + testQuotedPrintableUTF_8(); + } + + public void testQuotedPrintableIso8859_1() { + String charset = "iso-8859-1"; + String input = "Hello, here are some danish letters: =E6=F8=E5.\r\n" + + "Please check that you are able to remove soft " + + "line breaks and handle '=3D' =\r\ncharacters within the text. \r\n" + + "Just a sequence of non optimal characters to make " + + "it complete: !\"#$@[\\]^{|}=\r\n~\r\n\r\n" + + "Thanks\r\n" + + "Casper"; + String expected = "Hello, here are some danish letters: æøå.\r\n" + + "Please check that you are able to remove soft " + + "line breaks and handle '=' characters within the text. \r\n" + + "Just a sequence of non optimal characters to make " + + "it complete: !\"#$@[\\]^{|}~\r\n\r\n" + + "Thanks\r\n" + + "Casper"; + String output; + output = new String(BluetoothMapUtils.quotedPrintableToUtf8(input, charset)); + if(D) Log.d(TAG, "\nExpected: \n" + expected); + if(D) Log.d(TAG, "\nOutput: \n" + output); + assertTrue(output.equals(expected)); + } + + public void testQuotedPrintableUTF_8() { + String charset = "utf-8"; + String input = "Hello, here are some danish letters: =C3=A6=C3=B8=C3=A5.\r\n" + + "Please check that you are able to remove soft " + + "line breaks and handle '=3D' =\r\ncharacters within the text. \r\n" + + "Just a sequence of non optimal characters to make " + + "it complete: !\"#$@[\\]^{|}=\r\n~\r\n\r\n" + + "Thanks\r\n" + + "Casper"; + String expected = "Hello, here are some danish letters: æøå.\r\n" + + "Please check that you are able to remove soft " + + "line breaks and handle '=' characters within the text. \r\n" + + "Just a sequence of non optimal characters to make " + + "it complete: !\"#$@[\\]^{|}~\r\n\r\n" + + "Thanks\r\n" + + "Casper"; + String output; + output = new String(BluetoothMapUtils.quotedPrintableToUtf8(input, charset)); + if(D) Log.d(TAG, "\nExpected: \n" + expected); + if(D) Log.d(TAG, "\nOutput: \n" + output); + assertTrue(output.equals(expected)); + } + } diff --git a/tests/src/com/android/bluetooth/tests/BluetoothTestUtils.java b/tests/src/com/android/bluetooth/tests/BluetoothTestUtils.java new file mode 100644 index 000000000..4ef89e176 --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/BluetoothTestUtils.java @@ -0,0 +1,54 @@ +package com.android.bluetooth.tests; + +import android.annotation.TargetApi; +import android.bluetooth.BluetoothAdapter; +import android.os.Build; +import android.test.AndroidTestCase; +import android.util.Log; + + +@TargetApi(Build.VERSION_CODES.ECLAIR) +public class BluetoothTestUtils extends AndroidTestCase { + + protected static String TAG = "BluetoothTestUtils"; + protected static final boolean D = true; + + static final int POLL_TIME = 500; + static final int ENABLE_TIMEOUT = 5000; + + /** Helper to turn BT on. + * This method will either fail on an assert, or return with BT turned on. + * Behavior of getState() and isEnabled() are validated along the way. + */ + public static void enableBt(BluetoothAdapter adapter) { + if (adapter.getState() == BluetoothAdapter.STATE_ON) { + assertTrue(adapter.isEnabled()); + return; + } + assertEquals(BluetoothAdapter.STATE_OFF, adapter.getState()); + assertFalse(adapter.isEnabled()); + adapter.enable(); + for (int i=0; i<ENABLE_TIMEOUT/POLL_TIME; i++) { + int state = adapter.getState(); + switch (state) { + case BluetoothAdapter.STATE_ON: + assertTrue(adapter.isEnabled()); + Log.i(TAG, "Bluetooth enabled..."); + return; + case BluetoothAdapter.STATE_OFF: + Log.i(TAG, "STATE_OFF: Still waiting for enable to begin..."); + break; + default: + Log.i(TAG, "Status is: " + state); + assertEquals(BluetoothAdapter.STATE_TURNING_ON, adapter.getState()); + assertFalse(adapter.isEnabled()); + break; + } + try { + Thread.sleep(POLL_TIME); + } catch (InterruptedException e) {} + } + fail("enable() timeout"); + } + +} diff --git a/tests/src/com/android/bluetooth/tests/ISeqStepAction.java b/tests/src/com/android/bluetooth/tests/ISeqStepAction.java new file mode 100644 index 000000000..db66af2d3 --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/ISeqStepAction.java @@ -0,0 +1,13 @@ +package com.android.bluetooth.tests; + +import java.io.IOException; + +import javax.obex.HeaderSet; +import javax.obex.Operation; + +public interface ISeqStepAction { + + void execute(SeqStep step, HeaderSet request, Operation op) + throws IOException; + +} diff --git a/tests/src/com/android/bluetooth/tests/ISeqStepValidator.java b/tests/src/com/android/bluetooth/tests/ISeqStepValidator.java new file mode 100644 index 000000000..5819f06e7 --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/ISeqStepValidator.java @@ -0,0 +1,15 @@ +package com.android.bluetooth.tests; + +import java.io.IOException; + +import javax.obex.HeaderSet; +import javax.obex.Operation; + +/** + * Interface to validate test step result + */ +public interface ISeqStepValidator { + boolean validate(SeqStep step, HeaderSet response, Operation op) + throws IOException; + +} diff --git a/tests/src/com/android/bluetooth/tests/ITestSequenceBuilder.java b/tests/src/com/android/bluetooth/tests/ITestSequenceBuilder.java new file mode 100644 index 000000000..3314fd6c5 --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/ITestSequenceBuilder.java @@ -0,0 +1,11 @@ +package com.android.bluetooth.tests; + +public interface ITestSequenceBuilder { + + /** + * Add steps to a sequencer + * @param sequencer The sequencer the steps will be added to. + */ + public void build(TestSequencer sequencer); + +} diff --git a/tests/src/com/android/bluetooth/tests/ITestSequenceConfigurator.java b/tests/src/com/android/bluetooth/tests/ITestSequenceConfigurator.java new file mode 100644 index 000000000..d260d8b28 --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/ITestSequenceConfigurator.java @@ -0,0 +1,17 @@ +package com.android.bluetooth.tests; + +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; + +import javax.obex.ServerRequestHandler; + +public interface ITestSequenceConfigurator { + + /** Use this to customize a serverRequestHandler + * @param sequence A reference to the sequence to handle + * @param stopLatch a reference to a latch that must be count down, when test completes. + * @return Reference to the ServerRequestHandler. + */ + public ServerRequestHandler getObexServer(ArrayList<SeqStep> sequence, + CountDownLatch stopLatch); +} diff --git a/tests/src/com/android/bluetooth/tests/MapObexLevelTest.java b/tests/src/com/android/bluetooth/tests/MapObexLevelTest.java new file mode 100644 index 000000000..fb5de24bd --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/MapObexLevelTest.java @@ -0,0 +1,286 @@ +package com.android.bluetooth.tests; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; + +import javax.obex.HeaderSet; +import javax.obex.Operation; +import javax.obex.ServerRequestHandler; + +import junit.framework.Assert; +import android.annotation.TargetApi; +import android.bluetooth.BluetoothServerSocket; +import android.bluetooth.BluetoothSocket; +import android.net.LocalServerSocket; +import android.net.LocalSocket; +import android.os.Build; +import android.os.RemoteException; +import android.test.AndroidTestCase; +import android.util.Log; + +import com.android.bluetooth.BluetoothObexTransport; +import com.android.bluetooth.tests.TestSequencer.OPTYPE; + +@TargetApi(Build.VERSION_CODES.KITKAT) +public class MapObexLevelTest extends AndroidTestCase implements ITestSequenceConfigurator { + protected static String TAG = "MapObexLevelTest"; + protected static final boolean D = true; + protected static final boolean TRACE = false; + protected static final boolean DELAY_PASS_30_SEC = true; + + // 128 bit UUID for MAP MAS + static final byte[] MAS_TARGET = new byte[] { + (byte)0xBB, (byte)0x58, (byte)0x2B, (byte)0x40, + (byte)0x42, (byte)0x0C, (byte)0x11, (byte)0xDB, + (byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00, + (byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66 + }; + + // 128 bit UUID for MAP MNS + static final byte[] MNS_TARGET = new byte[] { + (byte)0xBB, (byte)0x58, (byte)0x2B, (byte)0x41, + (byte)0x42, (byte)0x0C, (byte)0x11, (byte)0xDB, + (byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00, + (byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66 + }; + + /* Message types */ + static final String TYPE_GET_FOLDER_LISTING = "x-obex/folder-listing"; + static final String TYPE_GET_MESSAGE_LISTING = "x-bt/MAP-msg-listing"; + static final String TYPE_GET_CONVO_LISTING = "x-bt/MAP-convo-listing"; + static final String TYPE_MESSAGE = "x-bt/message"; + static final String TYPE_SET_MESSAGE_STATUS = "x-bt/messageStatus"; + static final String TYPE_SET_NOTIFICATION_REGISTRATION = "x-bt/MAP-NotificationRegistration"; + static final String TYPE_MESSAGE_UPDATE = "x-bt/MAP-messageUpdate"; + static final String TYPE_GET_MAS_INSTANCE_INFORMATION = "x-bt/MASInstanceInformation"; + + public void testFolder() { + testLocalSockets(new buildFolderTestSeq()); + } + + public void testFolderServer() { + testServer(new buildFolderTestSeq()); + } + + public void testFolderClient() { + testClient(new buildFolderTestSeq()); + } + + protected class buildFolderTestSeq implements ITestSequenceBuilder { + @Override + public void build(TestSequencer sequencer) { + addConnectStep(sequencer); + + MapStepsFolder.addGoToMsgFolderSteps(sequencer); + + // MAP DISCONNECT Step + addDisconnectStep(sequencer); + } + } + + + public void testConvo() { + testLocalSockets(new buildConvoTestSeq()); + } + + public void testConvoServer() { + testServer(new buildConvoTestSeq()); + } + + public void testConvoClient() { + testClient(new buildConvoTestSeq()); + } + + class buildConvoTestSeq implements ITestSequenceBuilder { + @Override + public void build(TestSequencer sequencer) { + addConnectStep(sequencer); + + MapStepsFolder.addGoToMsgFolderSteps(sequencer); + + MapStepsConvo.addConvoListingSteps(sequencer); + + // MAP DISCONNECT Step + addDisconnectStep(sequencer); + } + } + + /** + * Run the test sequence using a local socket on a single device. + * Throughput around 4000 kbyte/s - with a larger OBEX package size. + * + * Downside: Unable to get a BT-snoop file... + */ + protected void testLocalSockets(ITestSequenceBuilder builder) { + mContext = this.getContext(); + MapTestData.init(mContext); + Log.i(TAG,"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 = new TestSequencer(clientTransport, serverTransport, this); + + builder.build(sequencer); + + //Debug.startMethodTracing("ObexTrace"); + assertTrue(sequencer.run(mContext)); + //Debug.stopMethodTracing(); + + clientSock.close(); + acceptSock.close(); + serverSock.close(); + } catch (IOException e) { + Log.e(TAG, "IOException", e); + } + } + + /** + * Server side of a dual device test using a Bluetooth Socket. + * Enables the possibility to get a BT-snoop file. + * If you need the btsnoop from the device which completes the test with success + * you need to add a delay after the test ends, and fetch the file before this delay + * expires. When the test completes, the Bluetooth subsystem will be restarted, causing + * a new bt-snoop to overwrite the one used in test. + */ + public void testServer(ITestSequenceBuilder builder) { + mContext = this.getContext(); + MapTestData.init(mContext); + Log.i(TAG,"Setting up sockets..."); + + try { + /* This will turn on BT and create a server socket on which accept can be called. */ + BluetoothServerSocket serverSocket=ObexTest.createServerSocket(BluetoothSocket.TYPE_L2CAP, true); + + Log.i(TAG, "Waiting for client to connect..."); + BluetoothSocket socket = serverSocket.accept(); + Log.i(TAG, "Client connected..."); + + BluetoothObexTransport serverTransport = new BluetoothObexTransport(socket); + + TestSequencer sequencer = new TestSequencer(null, serverTransport, this); + + builder.build(sequencer); + + //Debug.startMethodTracing("ObexTrace"); + assertTrue(sequencer.run(mContext)); + //Debug.stopMethodTracing(); + + serverSocket.close(); + socket.close(); + } catch (IOException e) { + Log.e(TAG, "IOException", e); + } + 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) {} + } + } + + /** + * Server side of a dual device test using a Bluetooth Socket. + * Enables the possibility to get a BT-snoop file. + * If you need the btsnoop from the device which completes the test with success + * you need to add a delay after the test ends, and fetch the file before this delay + * expires. When the test completes, the Bluetooth subsystem will be restarted, causing + * a new bt-snoop to overwrite the one used in test. + */ + public void testClient(ITestSequenceBuilder builder) { + mContext = this.getContext(); + MapTestData.init(mContext); + Log.i(TAG, "Setting up sockets..."); + + try { + /* This will turn on BT and connect */ + BluetoothSocket clientSock = + ObexTest.connectClientSocket(BluetoothSocket.TYPE_L2CAP, true, mContext); + + BluetoothObexTransport clientTransport = new BluetoothObexTransport(clientSock); + + TestSequencer sequencer = new TestSequencer(clientTransport, null, this); + + builder.build(sequencer); + + //Debug.startMethodTracing("ObexTrace"); + assertTrue(sequencer.run(mContext)); + //Debug.stopMethodTracing(); + + clientSock.close(); + } catch (IOException e) { + Log.e(TAG, "IOException", e); + } + 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) {} + } + } + + protected void addConnectStep(TestSequencer sequencer) { + SeqStep step; + + // MAP CONNECT Step + step = sequencer.addStep(OPTYPE.CONNECT, null); + HeaderSet hs = new HeaderSet(); + hs.setHeader(HeaderSet.TARGET, MAS_TARGET); + step.mReqHeaders = hs; + step.mValidator = new MapConnectValidator(); + //step.mServerPreAction = new MapAddSmsMessages(); // could take in parameters + } + + protected void addDisconnectStep(TestSequencer sequencer) { + sequencer.addStep(OPTYPE.DISCONNECT, ObexTest.getResponsecodevalidator()); + } + + /* Functions to validate results */ + + private class MapConnectValidator implements ISeqStepValidator { + @Override + public boolean validate(SeqStep step, HeaderSet response, Operation notUsed) + throws IOException { + Assert.assertNotNull(response); + byte[] who = (byte[])response.getHeader(HeaderSet.WHO); + Assert.assertNotNull(who); + Assert.assertTrue(Arrays.equals(who, MAS_TARGET)); + Assert.assertNotNull(response.getHeader(HeaderSet.CONNECTION_ID)); + return true; + } + } + + /** + * This is the function creating the Obex Server to be used in this class. + * Here we use a mocked version of the MapObexServer class + */ + @Override + public ServerRequestHandler getObexServer(ArrayList<SeqStep> sequence, + CountDownLatch stopLatch) { + try { + return new MapObexTestServer(mContext, sequence, stopLatch); + } catch (RemoteException e) { + Log.e(TAG, "exception", e); + fail("Unable to create MapObexTestServer"); + } + return null; + } + + +} + diff --git a/tests/src/com/android/bluetooth/tests/MapObexTestServer.java b/tests/src/com/android/bluetooth/tests/MapObexTestServer.java new file mode 100644 index 000000000..4fb6ba65d --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/MapObexTestServer.java @@ -0,0 +1,188 @@ +package com.android.bluetooth.tests; + +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; + +import javax.obex.HeaderSet; +import javax.obex.Operation; +import javax.obex.ResponseCodes; + +import junit.framework.Assert; +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; + +import com.android.bluetooth.map.BluetoothMapAccountItem; +import com.android.bluetooth.map.BluetoothMapContentObserver; +import com.android.bluetooth.map.BluetoothMapMasInstance; +import com.android.bluetooth.map.BluetoothMapObexServer; +import com.android.bluetooth.map.BluetoothMapUtils; +import com.android.bluetooth.map.BluetoothMnsObexClient; + +public class MapObexTestServer extends BluetoothMapObexServer { + + private static final String TAG = "MapObexTestServer"; + private static final boolean V = true; + + ArrayList<SeqStep> mSequence; + CountDownLatch mStopLatch; + + ObexTestDataHandler mDataHandler; + int mOperationIndex = 0; + + /* This needs to be static, as calling the super-constructor must be the first step. + * Alternatively add the account as constructor parameter, and create a builder + * function - factory pattern. */ +// private static BluetoothMapAccountItem mAccountMock = new BluetoothMapAccountItem("1", +// "TestAccount", +// "do.not.exist.package.name.and.never.used.anyway:-)", +// "info.guardianproject.otr.app.im.provider.bluetoothprovider", +// null, +// BluetoothMapUtils.TYPE.IM, +// null, +// null); + private static BluetoothMapAccountItem mAccountMock = null; + + /* MAP Specific instance variables + private final BluetoothMapContentObserver mObserver = null; + private final BluetoothMnsObexClient mMnsClient = null;*/ + + /* Test values, consider gathering somewhere else */ + private static final int MAS_ID = 0; + private static final int REMOTE_FEATURE_MASK = 0x07FFFFFF; + private static final BluetoothMapMasInstance mMasInstance = + new MockMasInstance(MAS_ID, REMOTE_FEATURE_MASK); + + public MapObexTestServer(final Context context, ArrayList<SeqStep> sequence, + CountDownLatch stopLatch) throws RemoteException { + + super(null, context, + new BluetoothMapContentObserver(context, + new BluetoothMnsObexClient( + BluetoothAdapter.getDefaultAdapter(). + getRemoteDevice("12:23:34:45:56:67"), null, null), + /* TODO: this will not work for single device test... */ + mMasInstance, + mAccountMock, /* Account */ + true) /* Enable SMS/MMS*/, + mMasInstance, + mAccountMock /* Account */, + true /* SMS/MMS enabled*/); + mSequence = sequence; + mDataHandler = new ObexTestDataHandler("(Server)"); + mStopLatch = stopLatch; + } + + /* OBEX operation handlers */ + @Override + public int onConnect(HeaderSet request, HeaderSet reply) { + Log.i(TAG,"onConnect()"); + int index; + int result = ResponseCodes.OBEX_HTTP_OK; + try { + index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue(); + mOperationIndex = index; + SeqStep step = mSequence.get(mOperationIndex); + Assert.assertNotNull("invalid step index!", step); + if(step.mServerPreAction != null) { + step.mServerPreAction.execute(step, request, null); + } + result = super.onConnect(request, reply); + } catch (Exception e) { + Log.e(TAG, "Exception in onConnect - aborting...", e); + result = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + // A read from null will produce exception to end the test. + } + return result; + } + + @Override + public void onDisconnect(HeaderSet request, HeaderSet reply) { + Log.i(TAG,"onDisconnect()"); + /* TODO: validate request headers, and set response headers */ + int index; + int result = ResponseCodes.OBEX_HTTP_OK; + try { + index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue(); + mOperationIndex = index; + SeqStep step = mSequence.get(mOperationIndex); + Assert.assertNotNull("invalid step index!", step); + if(step.mServerPreAction != null) { + step.mServerPreAction.execute(step, request, null); + } + super.onDisconnect(request, reply); + } catch (Exception e) { + Log.e(TAG, "Exception in onDisconnect - aborting...", e); + result = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + // A read from null will produce exception to end the test. + } + if(mOperationIndex >= (mSequence.size()-1)) { + /* End of test, signal test runner thread */ + Log.i(TAG, "Sending latch close signal..."); + mStopLatch.countDown(); + } else { + Log.i(TAG, "Got disconnect with mOperationCounter = " + mOperationIndex); + } + reply.responseCode = result; + } + + @Override + public int onPut(Operation operation) { + Log.i(TAG,"onPut()"); + int result = ResponseCodes.OBEX_HTTP_OK; + try{ + HeaderSet reqHeaders = operation.getReceivedHeader(); + int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue(); + mOperationIndex = index; + SeqStep step = mSequence.get(mOperationIndex); + Assert.assertNotNull("invalid step index!", step); + if(step.mServerPreAction != null) { + step.mServerPreAction.execute(step, reqHeaders, operation); + } + super.onPut(operation); + } catch (Exception e) { + Log.e(TAG, "Exception in onPut - aborting...", e); + result = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + // A read from null will produce exception to end the test. + } + if(result == ResponseCodes.OBEX_HTTP_OK) { + Log.i(TAG, "OBEX-HANDLER: operation complete success"); + } else { + Log.e(TAG, "OBEX-HANDLER: operation complete FAILED!"); + } + return result; + } + + @Override + public int onGet(Operation operation) { + Log.i(TAG,"onGet()"); + int result = ResponseCodes.OBEX_HTTP_OK; + try{ + HeaderSet reqHeaders = operation.getReceivedHeader(); + int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue(); + mOperationIndex = index; + SeqStep step = mSequence.get(mOperationIndex); + Assert.assertNotNull("invalid step index!", step); + if(step.mServerPreAction != null) { + step.mServerPreAction.execute(step, reqHeaders, operation); + } + super.onGet(operation); + } catch (Exception e) { + Log.e(TAG, "Exception in onGet - aborting...", e); + result = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; + // A read from null will produce exception to end the test. + } + if(result == ResponseCodes.OBEX_HTTP_OK) { + Log.i(TAG, "OBEX-HANDLER: operation complete success"); + } else { + Log.e(TAG, "OBEX-HANDLER: operation complete FAILED!"); + } + return result; + } + + + +} + diff --git a/tests/src/com/android/bluetooth/tests/MapStepsConvo.java b/tests/src/com/android/bluetooth/tests/MapStepsConvo.java new file mode 100644 index 000000000..f4288bb4f --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/MapStepsConvo.java @@ -0,0 +1,240 @@ +package com.android.bluetooth.tests; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.text.ParseException; +import java.util.Arrays; + +import javax.obex.HeaderSet; +import javax.obex.Operation; +import javax.obex.ResponseCodes; + +import junit.framework.Assert; +import android.util.Log; + +import com.android.bluetooth.map.BluetoothMapAppParams; +import com.android.bluetooth.map.BluetoothMapConvoListing; +import com.android.bluetooth.map.BluetoothMapConvoListingElement; +import com.android.bluetooth.map.BluetoothMapFolderElement; +import com.android.bluetooth.tests.TestSequencer.OPTYPE; + +public class MapStepsConvo { + private static final String TAG = "MapStepsConvo"; + + + protected static void addConvoListingSteps(TestSequencer sequencer) { + SeqStep step; + final int count = 5; + + // TODO: As we use the default message database, these tests will fail if the + // database has any content. + // To cope with this for now, the validation is disabled. + + /* Request the number of messages */ + step = addConvoListingStep(sequencer, + 0 /*maxListCount*/, + -1 /*listStartOffset*/, + null /*activityBegin*/, + null /*activityEnd*/, + -1 /*readStatus*/, + null /*recipient*/, + new MapConvoListValidator() + /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/); + /* Add messages and contacts for the entire sequence of tests */ + step.mServerPreAction = new MapTestData.MapAddSmsMessages(count); + + /* Request the XML for all conversations */ + step = addConvoListingStep(sequencer, + -1 /*maxListCount*/, + -1 /*listStartOffset*/, + null /*activityBegin*/, + null /*activityEnd*/, + -1 /*readStatus*/, + null /*recipient*/, + /*nearly impossible to validate due to auto assigned ID values*/ + new MapConvoListValidator() + /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/); + + step = addConvoListingStep(sequencer, + 2 /*maxListCount*/, + -1 /*listStartOffset*/, + null /*activityBegin*/, + null /*activityEnd*/, + -1 /*readStatus*/, + null /*recipient*/, + /*nearly impossible to validate due to auto assigned ID values*/ + new MapConvoListValidator() + /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/); + + step = addConvoListingStep(sequencer, + 2 /*maxListCount*/, + 1 /*listStartOffset*/, + null /*activityBegin*/, + null /*activityEnd*/, + -1 /*readStatus*/, + null /*recipient*/, + /*nearly impossible to validate due to auto assigned ID values*/ + new MapConvoListValidator() + /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/); + + step = addConvoListingStep(sequencer, + 3 /*maxListCount*/, + 2 /*listStartOffset*/, + null /*activityBegin*/, + null /*activityEnd*/, + -1 /*readStatus*/, + null /*recipient*/, + /*nearly impossible to validate due to auto assigned ID values*/ + new MapConvoListValidator() + /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/); + + step = addConvoListingStep(sequencer, + 5 /*maxListCount*/, + 1 /*listStartOffset*/, + MapTestData.TEST_ACTIVITY_BEGIN_STRING /*activityBegin*/, + null /*activityEnd*/, + -1 /*readStatus*/, + null /*recipient*/, + /*nearly impossible to validate due to auto assigned ID values*/ + new MapConvoListValidator() + /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/); + + step = addConvoListingStep(sequencer, + 5 /*maxListCount*/, + 0 /*listStartOffset*/, + MapTestData.TEST_ACTIVITY_BEGIN_STRING /*activityBegin*/, + MapTestData.TEST_ACTIVITY_END_STRING /*activityEnd*/, + -1 /*readStatus*/, + null /*recipient*/, + /*nearly impossible to validate due to auto assigned ID values*/ + new MapConvoListValidator() + /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/); + + step = addConvoListingStep(sequencer, + 5 /*maxListCount*/, + 1 /*listStartOffset*/, + MapTestData.TEST_ACTIVITY_BEGIN_STRING /*activityBegin*/, + null /*activityEnd*/, + 2/* read only */ /*readStatus*/, + null /*recipient*/, + /*nearly impossible to validate due to auto assigned ID values*/ + new MapConvoListValidator() + /*new MapConvoListValidator(MapTestData.TEST_NUM_CONTACTS)validator*/); + + /* TODO: Test the different combinations of filtering */ + } + + /** + * Use -1 or null to omit value in request + * @param sequencer + * @param maxListCount + * @param listStartOffset + * @param activityBegin + * @param activityEnd + * @param readStatus -1 omit value, 0 = no filtering, 1 = get unread only, 2 = get read only, + * 3 = 1+2 - hence get none... + * @param recipient substring of the recipient name + * @param validator + * @return a reference to the step added, for further decoration + */ + private static SeqStep addConvoListingStep(TestSequencer sequencer, int maxListCount, + int listStartOffset, String activityBegin, String activityEnd, + int readStatus, String recipient, ISeqStepValidator validator) { + SeqStep step; + BluetoothMapAppParams appParams = new BluetoothMapAppParams(); + try { + if(activityBegin != null) { + appParams.setFilterLastActivityBegin(activityBegin); + } + if(activityEnd != null) { + appParams.setFilterLastActivityEnd(activityEnd); + } + if(readStatus != -1) { + appParams.setFilterReadStatus(readStatus); + } + if(recipient != null) { + appParams.setFilterRecipient(recipient); + } + if(maxListCount != -1) { + appParams.setMaxListCount(maxListCount); + } + if(listStartOffset != -1) { + appParams.setStartOffset(listStartOffset); + } + } catch (ParseException e) { + Log.e(TAG, "unable to build appParams", e); + } + step = sequencer.addStep(OPTYPE.GET, null); + HeaderSet hs = new HeaderSet(); + hs.setHeader(HeaderSet.TYPE, MapObexLevelTest.TYPE_GET_CONVO_LISTING); + try { + hs.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.EncodeParams()); + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "ERROR", e); + Assert.fail(); + } + step.mReqHeaders = hs; + step.mValidator = validator; + return step; + } + + /* Functions to validate results */ + private static class MapConvoListValidator implements ISeqStepValidator { + + final BluetoothMapConvoListing mExpectedListing; + final int mExpectedSize; + + public MapConvoListValidator(BluetoothMapConvoListing listing) { + this.mExpectedListing = listing; + this.mExpectedSize = -1; + } + + public MapConvoListValidator(int convoListingSize) { + this.mExpectedListing = null; + this.mExpectedSize = convoListingSize; + } + + public MapConvoListValidator() { + this.mExpectedListing = null; + this.mExpectedSize = -1; + } + + @Override + public boolean validate(SeqStep step, HeaderSet response, Operation op) + throws IOException { + Assert.assertNotNull(op); + op.noBodyHeader(); + try { + // For some odd reason, the request will not be send before we start to read the + // reply data, hence we need to do this first? + BluetoothMapConvoListing receivedListing = new BluetoothMapConvoListing(); + receivedListing.appendFromXml(op.openInputStream()); + response = op.getReceivedHeader(); + byte[] appParamsRaw = (byte[])response.getHeader(HeaderSet.APPLICATION_PARAMETER); + Assert.assertNotNull(appParamsRaw); + BluetoothMapAppParams appParams; + appParams = new BluetoothMapAppParams(appParamsRaw); + Assert.assertNotNull(appParams); + Assert.assertNotNull(appParams.getDatabaseIdentifier()); + Assert.assertNotSame(BluetoothMapAppParams.INVALID_VALUE_PARAMETER, + appParams.getConvoListingSize()); + if(mExpectedSize >= 0) { + Assert.assertSame(mExpectedSize, appParams.getConvoListingSize()); + } + if(mExpectedListing != null) { + // Recursively compare + Assert.assertTrue(mExpectedListing.equals(receivedListing)); + Assert.assertSame(mExpectedListing.getList().size(), + appParams.getConvoListingSize()); + } + int responseCode = op.getResponseCode(); + Assert.assertEquals(ResponseCodes.OBEX_HTTP_OK, responseCode); + op.close(); + } catch (Exception e) { + Log.e(TAG,"",e); + Assert.fail(); + } + return true; + } + } +} diff --git a/tests/src/com/android/bluetooth/tests/MapStepsFolder.java b/tests/src/com/android/bluetooth/tests/MapStepsFolder.java new file mode 100644 index 000000000..11d27dcfb --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/MapStepsFolder.java @@ -0,0 +1,159 @@ +package com.android.bluetooth.tests; + +import java.io.IOException; + +import javax.obex.HeaderSet; +import javax.obex.Operation; +import javax.obex.ResponseCodes; + +import junit.framework.Assert; +import android.util.Log; + +import com.android.bluetooth.map.BluetoothMapAppParams; +import com.android.bluetooth.map.BluetoothMapFolderElement; +import com.android.bluetooth.tests.TestSequencer.OPTYPE; + +public class MapStepsFolder { + private final static String TAG = "MapStepsFolder"; + /** + * Request and expect the following folder structure: + * root + * telecom + * msg + * inbox + * outbox + * draft + * sent + * deleted + * + * The order in which they occur in the listing will not matter. + * @param sequencer + */ + protected static void addGoToMsgFolderSteps(TestSequencer sequencer) { + SeqStep step; + //BluetoothMapFolderElement rootDir = new BluetoothMapFolderElement("root", null); + + // MAP Get Folder Listing Steps + // The telecom folder + step = sequencer.addStep(OPTYPE.GET, null); + HeaderSet hs = new HeaderSet(); + hs.setHeader(HeaderSet.TYPE, MapObexLevelTest.TYPE_GET_FOLDER_LISTING); + step.mReqHeaders = hs; + step.mValidator = new MapBuildFolderStructurValidator(1, null); + + step = sequencer.addStep(OPTYPE.SET_PATH, ObexTest.getResponsecodevalidator()); + hs = new HeaderSet(); + hs.setHeader(HeaderSet.NAME, "telecom"); + step.mReqHeaders = hs; + step.mClientPostAction = new MapSetClientFolder("telecom"); + + + // The msg folder + step = sequencer.addStep(OPTYPE.GET, null); + hs = new HeaderSet(); + hs.setHeader(HeaderSet.TYPE, MapObexLevelTest.TYPE_GET_FOLDER_LISTING); + step.mReqHeaders = hs; + step.mValidator = new MapBuildFolderStructurValidator(1, null); + + step = sequencer.addStep(OPTYPE.SET_PATH, ObexTest.getResponsecodevalidator()); + hs = new HeaderSet(); + hs.setHeader(HeaderSet.NAME, "msg"); + step.mReqHeaders = hs; + step.mClientPostAction = new MapSetClientFolder("msg"); + + // The msg folder + step = sequencer.addStep(OPTYPE.GET, null); + hs = new HeaderSet(); + hs.setHeader(HeaderSet.TYPE, MapObexLevelTest.TYPE_GET_FOLDER_LISTING); + step.mReqHeaders = hs; + step.mValidator = new MapBuildFolderStructurValidator(5, buildDefaultFolderStructure()); + } + + /** + * Sets the current folder on the client, to the folder name specified in the constructor. + * TODO: Could be extended to be able to navigate back and forth in the folder structure. + */ + private static class MapSetClientFolder implements ISeqStepAction { + final String mFolderName; + public MapSetClientFolder(String folderName) { + super(); + this.mFolderName = folderName; + } + @Override + public void execute(SeqStep step, HeaderSet request, Operation op) + throws IOException { + MapBuildFolderStructurValidator.sCurrentFolder = + MapBuildFolderStructurValidator.sCurrentFolder.getSubFolder(mFolderName); + Assert.assertNotNull(MapBuildFolderStructurValidator.sCurrentFolder); + Log.i(TAG, "MapSetClientFolder(): Current path: " + + MapBuildFolderStructurValidator.sCurrentFolder.getFullPath()); + } + } + + /* Functions to validate results */ + private static class MapBuildFolderStructurValidator implements ISeqStepValidator { + + final int mExpectedListingSize; + static BluetoothMapFolderElement sCurrentFolder = null; + final BluetoothMapFolderElement mExpectedFolderElement; + + public MapBuildFolderStructurValidator(int mExpectedListingSize, + BluetoothMapFolderElement folderElement) { + super(); + if(sCurrentFolder == null) { + sCurrentFolder = new BluetoothMapFolderElement("root", null); + } + this.mExpectedListingSize = mExpectedListingSize; + this.mExpectedFolderElement = folderElement; + } + + + @Override + public boolean validate(SeqStep step, HeaderSet response, Operation op) + throws IOException { + Assert.assertNotNull(op); + op.noBodyHeader(); + try { + // For some odd reason, the request will not be send before we start to read the + // reply data, hence we need to do this first? + sCurrentFolder.appendSubfolders(op.openInputStream()); + response = op.getReceivedHeader(); + byte[] appParamsRaw = (byte[])response.getHeader(HeaderSet.APPLICATION_PARAMETER); + Assert.assertNotNull(appParamsRaw); + BluetoothMapAppParams appParams; + appParams = new BluetoothMapAppParams(appParamsRaw); + Assert.assertNotNull(appParams); + if(mExpectedFolderElement != null) { + // Recursively compare + Assert.assertTrue(mExpectedFolderElement.compareTo(sCurrentFolder.getRoot()) + == 0); + } + int responseCode = op.getResponseCode(); + Assert.assertEquals(ResponseCodes.OBEX_HTTP_OK, responseCode); + op.close(); + } catch (Exception e) { + Log.e(TAG,"",e); + Assert.fail(); + } + return true; + } + + } + + + private static BluetoothMapFolderElement buildDefaultFolderStructure(){ + BluetoothMapFolderElement root = + new BluetoothMapFolderElement("root", null); // This will be the root element + BluetoothMapFolderElement tmpFolder; + tmpFolder = root.addFolder("telecom"); // root/telecom + tmpFolder = tmpFolder.addFolder("msg"); // root/telecom/msg + tmpFolder.addFolder("inbox"); // root/telecom/msg/inbox + tmpFolder.addFolder("outbox"); // root/telecom/msg/outbox + tmpFolder.addFolder("sent"); // root/telecom/msg/sent + tmpFolder.addFolder("deleted"); // root/telecom/msg/deleted + tmpFolder.addFolder("draft"); // root/telecom/msg/draft + return root; + } + + +} diff --git a/tests/src/com/android/bluetooth/tests/MapTestData.java b/tests/src/com/android/bluetooth/tests/MapTestData.java new file mode 100644 index 000000000..7cb723bf2 --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/MapTestData.java @@ -0,0 +1,286 @@ +package com.android.bluetooth.tests; + +import java.io.IOException; +import java.util.Date; + +import javax.obex.HeaderSet; +import javax.obex.Operation; + +import android.annotation.TargetApi; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.net.Uri; +import android.provider.ContactsContract; +import android.provider.Telephony.Sms; +import android.test.AndroidTestCase; +import android.util.Log; + +import com.android.bluetooth.map.BluetoothMapConvoContactElement; +import com.android.bluetooth.map.BluetoothMapConvoListing; +import com.android.bluetooth.map.BluetoothMapConvoListingElement; +import com.android.bluetooth.mapapi.BluetoothMapContract; + +/** + * Class to hold test data - both the server side data to insert into the databases, and the + * validation data to validate the result, when reading back the data. + * + * Should be data only, not operation specific functionality (client). + * + * Please try to keep useful functionality call-able from a test case, to make it possible + * to call a single test case to e.g. inject some contacts or messages into the database. + * + */ +@TargetApi(20) +public class MapTestData extends AndroidTestCase { + private static final String TAG = "MapTestData"; + + /* Test validation variables */ + static final String TEST_CONTACT_NAME = "Jesus Überboss"; + static final String TEST_CONTACT_PHONE = "55566688"; + static final String TEST_CONTACT_EMAIL = "boss@the.skyes"; + static final int TEST_NUM_CONTACTS = 3; + + static final int TEST_ADD_CONTACT_PER_ITERATIONS = 4; + /* I do know this function is deprecated, but I'm unable to find a good alternative + * except from taking a copy of the Date.UTC function as suggested. */ + // NOTE: This will only set the data on the message - not the lastActivity on SMS/MMS threads + static final long TEST_ACTIVITY_BEGIN = Date.UTC( + 2014-1900, + 8-1, /* month 0-11*/ + 22, /*day 1-31 */ + 22, /*hour*/ + 15, /*minute*/ + 20 /*second*/); + + static final String TEST_ACTIVITY_BEGIN_STRING = "20150102T150047"; + static final String TEST_ACTIVITY_END_STRING = "20160102T150047"; + + static final int TEST_ACTIVITY_INTERVAL = 5*60*1000; /*ms*/ + + static Context sContext = null; + public static void init(Context context){ + sContext = context; + } + /** + * Adds messages to the SMS message database. + */ + public static class MapAddSmsMessages implements ISeqStepAction { + int mCount; + /** + * + * @param count the number of iterations to execute + */ + public MapAddSmsMessages(int count) { + mCount = count; + } + + @Override + public void execute(SeqStep step, HeaderSet request, Operation op) + throws IOException { + int count = mCount; // Number of messages in each conversation + ContentResolver resolver = sContext.getContentResolver(); + + // Insert some messages + insertTestMessages(resolver, step.index, count); + + // Cleanup if needed to avoid duplicates + deleteTestContacts(resolver); + + // And now add the contacts + setupTestContacts(resolver); + } + } + + /** + * TODO: Only works for filter on TEST_CONTACT_NAME + * @param maxCount + * @param offset + * @param filterContact + * @param read + * @param reportRead + * @param msgCount + * @return + */ + public static BluetoothMapConvoListing getConvoListingReference(int maxCount, int offset, + boolean filterContact, boolean read, boolean reportRead, int msgCount){ + BluetoothMapConvoListing list = new BluetoothMapConvoListing(); + BluetoothMapConvoListingElement element; + BluetoothMapConvoContactElement contact; + element = new BluetoothMapConvoListingElement(); + element.setRead(read, reportRead); + element.setVersionCounter(0); + contact = new BluetoothMapConvoContactElement(); + contact.setName(TEST_CONTACT_NAME); + contact.setLastActivity(TEST_ACTIVITY_BEGIN + + msgCount*TEST_ADD_CONTACT_PER_ITERATIONS*TEST_ACTIVITY_INTERVAL); + element.addContact(contact); + list.add(element); + return null; + } + + public static void insertTestMessages(ContentResolver resolver, int tag, int count) { + ContentValues values[] = new ContentValues[count*4]; // 4 messages/iteration + long date = TEST_ACTIVITY_BEGIN; + Log.i(TAG, "Preparing messages... with data = " + date); + + for (int x = 0;x < count;x++){ + /* NOTE: Update TEST_ADD_CONTACT_PER_ITERATIONS if more messages are added */ + ContentValues item = new ContentValues(5); + item.put("address", "98765432"); + item.put("body", "test message " + x + " step index: " + tag); + item.put("date", date+=TEST_ACTIVITY_INTERVAL); + item.put("read", "0"); + if(x%2 == 0) { + item.put("type", Sms.MESSAGE_TYPE_INBOX); + } else { + item.put("type", Sms.MESSAGE_TYPE_SENT); + } + values[x] = item; + + item = new ContentValues(5); + item.put("address", "23456780"); + item.put("body", "test message " + x + " step index: " + tag); + item.put("date", date += TEST_ACTIVITY_INTERVAL); + item.put("read", "0"); + if(x%2 == 0) { + item.put("type", Sms.MESSAGE_TYPE_INBOX); + } else { + item.put("type", Sms.MESSAGE_TYPE_SENT); + } + values[count+x] = item; + + item = new ContentValues(5); + item.put("address", "+4523456780"); + item.put("body", "test message "+x+" step index: " + tag); + item.put("date", date += TEST_ACTIVITY_INTERVAL); + item.put("read", "0"); + if(x%2 == 0) { + item.put("type", Sms.MESSAGE_TYPE_INBOX); + } else { + item.put("type", Sms.MESSAGE_TYPE_SENT); + } + values[2*count+x] = item; + + /* This is the message used for test */ + item = new ContentValues(5); + item.put("address", TEST_CONTACT_PHONE); + item.put("body", "test message "+x+" step index: " + tag); + item.put("date", date += TEST_ACTIVITY_INTERVAL); + item.put("read", "0"); + if(x%2 == 0) { + item.put("type", Sms.MESSAGE_TYPE_INBOX); + } else { + item.put("type", Sms.MESSAGE_TYPE_SENT); + } + values[3*count+x] = item; + } + + Log.i(TAG, "Starting bulk insert..."); + resolver.bulkInsert(Uri.parse("content://sms"), values); + Log.i(TAG, "Bulk insert done."); + } + + /** + * Insert a few contacts in the main contact database, using a test account. + */ + public static void setupTestContacts(ContentResolver resolver){ + /*TEST_NUM_CONTACTS must be updated if this function is changed */ + insertContact(resolver, "Hans Hansen", "98765432", "hans@hansens.global"); + insertContact(resolver, "Helle Børgesen", "23456780", "hb@gmail.com"); + insertContact(resolver, TEST_CONTACT_NAME, TEST_CONTACT_PHONE, TEST_CONTACT_EMAIL); + } + + /** + * Helper function to insert a contact + * @param name + * @param phone + * @param email + */ + private static void insertContact(ContentResolver resolver, String name, String phone, String email) { + // Get the account info + //Cursor c = resolver.query(uri, projection, selection, selectionArgs, sortOrder) + ContentValues item = new ContentValues(3); + item.put(ContactsContract.RawContacts.ACCOUNT_TYPE, "test_account"); + item.put(ContactsContract.RawContacts.ACCOUNT_NAME, "MAP account"); + Uri uri = resolver.insert(ContactsContract.RawContacts.CONTENT_URI, item); + Log.i(TAG, "Inserted RawContact: " + uri); + long rawId = Long.parseLong(uri.getLastPathSegment()); + + //Now add contact information + item = new ContentValues(3); + item.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + item.put(ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE); + item.put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, + name); + resolver.insert(ContactsContract.Data.CONTENT_URI, item); + + if(phone != null) { + item = new ContentValues(3); + item.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + item.put(ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE); + item.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone); + resolver.insert(ContactsContract.Data.CONTENT_URI, item); + } + + if(email != null) { + item = new ContentValues(3); + item.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); + item.put(ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE); + item.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email); + resolver.insert(ContactsContract.Data.CONTENT_URI, item); + } + } + + /** + * Delete all contacts belonging to the test_account. + */ + public static void deleteTestContacts(ContentResolver resolver){ + resolver.delete(ContactsContract.RawContacts.CONTENT_URI, + ContactsContract.RawContacts.ACCOUNT_TYPE + "=\"test_account\"", null); + } + + /**************************************************************************** + * Small test cases to trigger the functionality without running a sequence. + ****************************************************************************/ + /** + * Insert a few contacts in the main contact database, using a test account. + */ + public void testInsertMessages() { + ContentResolver resolver = mContext.getContentResolver(); + insertTestMessages(resolver, 1234, 10); + } + + public void testInsert1000Messages() { + ContentResolver resolver = mContext.getContentResolver(); + insertTestMessages(resolver, 1234, 1000); + } + + /** + * Insert a few contacts in the main contact database, using a test account. + */ + public void testSetupContacts() { + ContentResolver resolver = mContext.getContentResolver(); + setupTestContacts(resolver); + } + + /** + * Delete all contacts belonging to the test_account. + */ + public void testDeleteTestContacts() { + ContentResolver resolver = mContext.getContentResolver(); + deleteTestContacts(resolver); + } + + public void testSetup1000Contacts() { + ContentResolver resolver = mContext.getContentResolver(); + for(int i = 0; i < 1000; i++) { + insertContact(resolver, "Hans Hansen " + i, + "98765431" + i, "hans" + i + "@hansens.global"); + } + } + +} diff --git a/tests/src/com/android/bluetooth/tests/MockMasInstance.java b/tests/src/com/android/bluetooth/tests/MockMasInstance.java new file mode 100644 index 000000000..07de8e493 --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/MockMasInstance.java @@ -0,0 +1,30 @@ +package com.android.bluetooth.tests; + +import com.android.bluetooth.map.BluetoothMapMasInstance; +import junit.framework.Assert; + +public class MockMasInstance extends BluetoothMapMasInstance { + + private final int mMasId; + private final int mRemoteFeatureMask; + + public MockMasInstance(int masId, int remoteFeatureMask) { + super(); + this.mMasId = masId; + this.mRemoteFeatureMask = remoteFeatureMask; + } + + public int getMasId() { + return mMasId; + } + + @Override + public int getRemoteFeatureMask() { + return mRemoteFeatureMask; + } + + @Override + public void restartObexServerSession() { + Assert.fail("restartObexServerSession() should not occur"); + } +} diff --git a/tests/src/com/android/bluetooth/tests/ObexPipeTransport.java b/tests/src/com/android/bluetooth/tests/ObexPipeTransport.java index 3bb980914..28625d57a 100644 --- a/tests/src/com/android/bluetooth/tests/ObexPipeTransport.java +++ b/tests/src/com/android/bluetooth/tests/ObexPipeTransport.java @@ -26,12 +26,12 @@ import java.io.PipedOutputStream; import javax.obex.ObexTransport; public class ObexPipeTransport implements ObexTransport { - PipedInputStream mInStream; - PipedOutputStream mOutStream; + InputStream mInStream; + OutputStream mOutStream; boolean mEnableSrm; - public ObexPipeTransport(PipedInputStream inStream, - PipedOutputStream outStream, boolean enableSrm) { + public ObexPipeTransport(InputStream inStream, + OutputStream outStream, boolean enableSrm) { mInStream = inStream; mOutStream = outStream; mEnableSrm = enableSrm; @@ -74,12 +74,12 @@ public class ObexPipeTransport implements ObexTransport { return true; } - public int getMaxTxPacketSize() { - return 15432; + public int getMaxTransmitPacketSize() { + return 3*15432; } - public int getMaxRxPacketSize() { - return 23450; + public int getMaxReceivePacketSize() { + return 2*23450; } @Override @@ -88,3 +88,4 @@ public class ObexPipeTransport implements ObexTransport { } } + diff --git a/tests/src/com/android/bluetooth/tests/ObexTest.java b/tests/src/com/android/bluetooth/tests/ObexTest.java index ad45522c2..b90ea14c4 100644 --- a/tests/src/com/android/bluetooth/tests/ObexTest.java +++ b/tests/src/com/android/bluetooth/tests/ObexTest.java @@ -22,41 +22,35 @@ import java.io.PipedOutputStream; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; -import javax.obex.ClientSession; import javax.obex.HeaderSet; -import javax.obex.ObexPacket; import javax.obex.ObexTransport; import javax.obex.Operation; import javax.obex.ResponseCodes; -import javax.obex.ServerSession; +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.nfc.cardemulation.OffHostApduService; +import android.net.LocalServerSocket; +import android.net.LocalSocket; import android.os.Build; import android.os.Debug; -import android.os.Handler; -import android.os.Handler.Callback; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; 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.sdp.SdpMasRecord; -import com.android.bluetooth.tests.ObexTest.TestSequencer.OPTYPE; -import com.android.bluetooth.tests.ObexTest.TestSequencer.SeqStep; +import com.android.bluetooth.tests.TestSequencer.OPTYPE; /** * Test either using the reference ril without a modem, or using a RIL implementing the @@ -64,42 +58,47 @@ import com.android.bluetooth.tests.ObexTest.TestSequencer.SeqStep; * */ @TargetApi(Build.VERSION_CODES.KITKAT) -public class ObexTest extends AndroidTestCase { +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/10); + new ObexTestParams(2*8092, 0, 2*1024*1024); private static final ObexTestParams throttle100Params = - new ObexTestParams(2*8092, 100000, 2*1024*1024/10); + 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/1000); + new ObexTestParams(2*8092, 0, 100*1024*1024); - private static final int SMALL_OPERATION_COUNT = 1000/100; + 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 = "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 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, @@ -109,10 +108,6 @@ public class ObexTest extends AndroidTestCase { private Context mContext = null; private int mChannelType = 0; - public static final int STEP_INDEX_HEADER = 0xF1; /*0xFE*/ - private static final int ENABLE_TIMEOUT = 5000; - private static final int POLL_TIME = 500; - public ObexTest() { super(); } @@ -121,6 +116,8 @@ public class ObexTest extends AndroidTestCase { * 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(); @@ -147,58 +144,97 @@ public class ObexTest extends AndroidTestCase { TestSequencer sequencer = createBtPayloadTestSequence(clientTransport, serverTransport); //Debug.startMethodTracing("ObexTrace"); - assertTrue(sequencer.run()); + 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); + TestSequencer sequencer = new TestSequencer(clientTransport, serverTransport, this); SeqStep step; - step = sequencer.addStep(OPTYPE.CONNECT); + step = sequencer.addStep(OPTYPE.CONNECT, sResponseCodeValidator); + if(false){ - step = sequencer.addStep(OPTYPE.PUT); + step = sequencer.addStep(OPTYPE.PUT, sDataValidator); step.mParams = defaultParams; step.mUseSrm = true; - step = sequencer.addStep(OPTYPE.GET); + step = sequencer.addStep(OPTYPE.GET, sDataValidator); step.mParams = defaultParams; step.mUseSrm = true; -if(true){ - step = sequencer.addStep(OPTYPE.PUT); + + step = sequencer.addStep(OPTYPE.PUT, sDataValidator); step.mParams = throttle100Params; step.mUseSrm = true; - step = sequencer.addStep(OPTYPE.GET); + step = sequencer.addStep(OPTYPE.GET, sDataValidator); step.mParams = throttle100Params; step.mUseSrm = true; for(int i=0; i<SMALL_OPERATION_COUNT; i++){ - step = sequencer.addStep(OPTYPE.PUT); + step = sequencer.addStep(OPTYPE.PUT, sDataValidator); step.mParams = smallParams; step.mUseSrm = true; - step = sequencer.addStep(OPTYPE.GET); + step = sequencer.addStep(OPTYPE.GET, sDataValidator); step.mParams = smallParams; step.mUseSrm = true; } +} - step = sequencer.addStep(OPTYPE.PUT); + step = sequencer.addStep(OPTYPE.PUT, sDataValidator); step.mParams = hugeParams; step.mUseSrm = true; - step = sequencer.addStep(OPTYPE.GET); + step = sequencer.addStep(OPTYPE.GET, sDataValidator); step.mParams = hugeParams; step.mUseSrm = true; - } - step = sequencer.addStep(OPTYPE.DISCONNECT); + step = sequencer.addStep(OPTYPE.DISCONNECT, sResponseCodeValidator); return sequencer; } @@ -206,24 +242,98 @@ if(true){ private TestSequencer createBtConnectTestSequence(ObexTransport clientTransport, ObexTransport serverTransport) throws IOException { - TestSequencer sequencer = new TestSequencer(clientTransport, serverTransport); + TestSequencer sequencer = new TestSequencer(clientTransport, serverTransport, this); SeqStep step; - step = sequencer.addStep(OPTYPE.CONNECT); + step = sequencer.addStep(OPTYPE.CONNECT, sResponseCodeValidator); - step = sequencer.addStep(OPTYPE.PUT); + step = sequencer.addStep(OPTYPE.PUT, sDataValidator); step.mParams = smallParams; step.mUseSrm = true; - step = sequencer.addStep(OPTYPE.GET); + step = sequencer.addStep(OPTYPE.GET, sDataValidator); step.mParams = smallParams; step.mUseSrm = true; - step = sequencer.addStep(OPTYPE.DISCONNECT); + step = sequencer.addStep(OPTYPE.DISCONNECT, sResponseCodeValidator); return sequencer; } + + /** + * Use this validator to validate operation response codes. E.g. for OBEX CONNECT and + * DISCONNECT operations. + * Expects HeaderSet to be valid, and Operation to be null. + */ + public static ISeqStepValidator getResponsecodevalidator() { + return sResponseCodeValidator; + } + + /** + * Use this validator to validate (and read/write data) for OBEX PUT and GET operations. + * Expects Operation to be valid, and HeaderSet to be null. + */ + public static ISeqStepValidator getDatavalidator() { + return sDataValidator; + } + + /** + * Use this validator to validate operation response codes. E.g. for OBEX CONNECT and + * DISCONNECT operations. + * Expects HeaderSet to be valid, and Operation to be null. + */ + private static class ResponseCodeValidator implements ISeqStepValidator { + + protected static boolean validateHeaderSet(HeaderSet headers, HeaderSet expected) + throws IOException { + if(headers.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) { + Log.e(TAG,"Wrong ResponseCode: " + headers.getResponseCode()); + Assert.assertTrue(false); + return false; + } + return true; + } + + @Override + public boolean validate(SeqStep step, HeaderSet response, Operation op) + throws IOException { + if(response == null) { + if(op.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) { + Log.e(TAG,"Wrong ResponseCode: " + op.getResponseCode()); + Assert.assertTrue(false); + return false; + } + return true; + } + return validateHeaderSet(response, step.mResHeaders); + } + } + + /** + * Use this validator to validate (and read/write data) for OBEX PUT and GET operations. + * Expects Operation to ve valid, and HeaderSet to be null. + */ + private static class DataValidator implements ISeqStepValidator { + @Override + public boolean validate(SeqStep step, HeaderSet notUsed, Operation op) + throws IOException { + Assert.assertNotNull(op); + if(step.mType == OPTYPE.GET) { + op.noBodyHeader(); + sDataHandler.readData(op.openDataInputStream(), step.mParams); + } else if (step.mType == OPTYPE.PUT) { + sDataHandler.writeData(op.openDataOutputStream(), step.mParams); + } + int responseCode = op.getResponseCode(); + Log.i(TAG, "response code: " + responseCode); + HeaderSet response = op.getReceivedHeader(); + ResponseCodeValidator.validateHeaderSet(response, step.mResHeaders); + op.close(); + return true; + } + } + public void testBtServerL2cap() { testBtServer(BluetoothSocket.TYPE_L2CAP, false, SequencerType.SEQ_TYPE_PAYLOAD); } @@ -276,7 +386,7 @@ if(true){ SequencerType.SEQ_TYPE_CONNECT_DISCONNECT); try { // We give the server 100ms to allow adding SDP record - Thread.sleep(100); + Thread.sleep(150); } catch (InterruptedException e) { Log.e(TAG,"Exception while waiting...",e); } @@ -303,7 +413,7 @@ if(true){ SequencerType.SEQ_TYPE_CONNECT_DISCONNECT); try { // We give the server 100ms to allow adding SDP record - Thread.sleep(100); + Thread.sleep(250); } catch (InterruptedException e) { Log.e(TAG,"Exception while waiting...",e); } @@ -327,16 +437,17 @@ if(true){ Log.e(TAG,"No Bluetooth Device!"); assertTrue(false); } - enableBt(bt); + BluetoothTestUtils.enableBt(bt); BluetoothServerSocket serverSocket=null; if(type == BluetoothSocket.TYPE_L2CAP) { if(useSdp == true) { - serverSocket = bt.listenUsingL2capOn(L2CAP_PSM); - } else { serverSocket = bt.listenUsingL2capOn( BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP); + } else { + serverSocket = bt.listenUsingL2capOn(L2CAP_PSM); } l2capPsm = serverSocket.getChannel(); + Log.d(TAG, "L2CAP createde, PSM: " + l2capPsm); } else if(type == BluetoothSocket.TYPE_RFCOMM) { if(useSdp == true) { serverSocket = bt.listenUsingInsecureRfcommOn( @@ -345,19 +456,25 @@ if(true){ serverSocket = bt.listenUsingInsecureRfcommOn(RFCOMM_CHANNEL); } rfcommChannel = serverSocket.getChannel(); + Log.d(TAG, "RFCOMM createde, Channel: " + rfcommChannel); } else { fail("Invalid transport type!"); } if(useSdp == true) { - /* We use the MAP service record to be able to */ + /* We use the MAP service record to be able to set rfcomm and l2cap channels */ // TODO: We need to free this - if(sSdpHandle < 0) { - sSdpHandle = SdpManager.getDefaultManager().createMapMasRecord(SDP_SERVER_NAME, - SDP_MAS_ID, rfcommChannel, l2capPsm, - SDP_VERSION, SDP_MSG_TYPES, (int)(SDP_FEATURES & 0xffffffff)); + if(sSdpHandle >= 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; } @@ -373,7 +490,7 @@ if(true){ */ private void testBtServer(int type, boolean useSdp, SequencerType sequencerType) { mContext = this.getContext(); - System.out.println("Starting BT Server..."); + Log.d(TAG,"Starting BT Server..."); if(TRACE) Debug.startMethodTracing("ServerSide"); try { @@ -398,7 +515,7 @@ if(true){ } //Debug.startMethodTracing("ObexTrace"); - assertTrue(sequencer.run()); + assertTrue(sequencer.run(mContext)); //Debug.stopMethodTracing(); // Same as below... serverTransport.close(); // This is done by the obex server socket.close(); @@ -436,7 +553,7 @@ if(true){ Log.e(TAG,"No Bluetooth Device!"); assertTrue(false); } - enableBt(bt); + BluetoothTestUtils.enableBt(bt); BluetoothDevice serverDevice = bt.getRemoteDevice(SERVER_ADDRESS); if(useSdp == true) { @@ -487,9 +604,9 @@ if(true){ } //Debug.startMethodTracing("ObexTrace"); - assertTrue(sequencer.run()); + assertTrue(sequencer.run(mContext)); //Debug.stopMethodTracing(); - // socket.close(); shall be closed by the obex client + socket.close(); // Only the streams are closed by the obex client sequencer.shutdown(); } catch (IOException e) { @@ -591,336 +708,13 @@ if(true){ return broadcastReceiver.getMasRecord(); } - /** Helper to turn BT on. - * This method will either fail on an assert, or return with BT turned on. - * Behavior of getState() and isEnabled() are validated along the way. - */ - public static void enableBt(BluetoothAdapter adapter) { - if (adapter.getState() == BluetoothAdapter.STATE_ON) { - assertTrue(adapter.isEnabled()); - return; - } - assertEquals(BluetoothAdapter.STATE_OFF, adapter.getState()); - assertFalse(adapter.isEnabled()); - adapter.enable(); - for (int i=0; i<ENABLE_TIMEOUT/POLL_TIME; i++) { - switch (adapter.getState()) { - case BluetoothAdapter.STATE_ON: - assertTrue(adapter.isEnabled()); - return; - case BluetoothAdapter.STATE_OFF: - Log.i(TAG, "STATE_OFF: Still waiting for enable to begin..."); - break; - default: - assertEquals(BluetoothAdapter.STATE_TURNING_ON, adapter.getState()); - assertFalse(adapter.isEnabled()); - break; - } - try { - Thread.sleep(POLL_TIME); - } catch (InterruptedException e) {} - } - fail("enable() timeout"); - Log.i(TAG, "Bluetooth enabled..."); + @Override + public ServerRequestHandler getObexServer(ArrayList<SeqStep> sequence, + CountDownLatch stopLatch) { + return new ObexTestServer(sequence, stopLatch); } - public static class TestSequencer implements Callback { - - private final static int MSG_ID_TIMEOUT = 0x01; - private final static int TIMEOUT_VALUE = 100*2000; // ms - private ArrayList<SeqStep> mSequence = null; - private HandlerThread mHandlerThread = null; - private Handler mMessageHandler = null; - private ObexTransport mClientTransport; - private ObexTransport mServerTransport; - - private ClientSession mClientSession; - private ServerSession mServerSession; - ObexTestDataHandler mDataHandler; - - public enum OPTYPE {CONNECT, PUT, GET, DISCONNECT}; - - - public class SeqStep { - /** - * Test step class to define the operations to be tested. - * Some of the data in these test steps will be modified during - * test - e.g. the HeaderSets will be modified to enable SRM - * and/or carry test information - */ - /* Operation type - Connect, Get, Put etc. */ - public OPTYPE mType; - /* The headers to send in the request - and validate on server side */ - public HeaderSet mReqHeaders = null; - /* The headers to send in the response - and validate on client side */ - public HeaderSet mResHeaders = null; - /* Use SRM */ - public boolean mUseSrm = false; - /* The amount of data to include in the body */ - public ObexTestParams mParams = null; - /* The offset into the data where the un-pause signal is to be sent */ - public int mUnPauseOffset = -1; - /* The offset into the data where the Abort request is to be sent */ - public int mAbortOffset = -1; - /* The side to perform Abort */ - public boolean mServerSideAbout = false; - /* The ID of the test step */ - private int mId; - - /* Arrays to hold expected sequence of request/response packets. */ - public ArrayList<ObexPacket> mRequestPackets = null; - public ArrayList<ObexPacket> mResponsePackets = null; - - public int index = 0; /* requests with same index are executed in parallel - (without waiting for a response) */ - - public SeqStep(OPTYPE type) { - mRequestPackets = new ArrayList<ObexPacket>(); - mResponsePackets = new ArrayList<ObexPacket>(); - mType = type; - } - - /* TODO: Consider to build these automatically based on the operations - * to be performed. Validate using utility functions - not strict - * binary compare.*/ - public void addObexPacketSet(ObexPacket request, ObexPacket response) { - mRequestPackets.add(request); - mResponsePackets.add(response); - } - } - - public TestSequencer(ObexTransport clientTransport, ObexTransport serverTransport) - throws IOException { - /* Setup the looper thread to handle messages */ -// mHandlerThread = new HandlerThread("TestTimeoutHandler", -// android.os.Process.THREAD_PRIORITY_BACKGROUND); -// mHandlerThread.start(); -// Looper testLooper = mHandlerThread.getLooper(); -// mMessageHandler = new Handler(testLooper, this); - mClientTransport = clientTransport; - mServerTransport = serverTransport; - - //TODO: fix looper cleanup on server - crash after 464 iterations - related to prepare? - - /* Initialize members */ - mSequence = new ArrayList<SeqStep>(); - mDataHandler = new ObexTestDataHandler("(Client)"); - } - - /** - * Add a test step to the sequencer. - * @param type the OBEX operation to perform. - * @return the created step, which can be decorated before execution. - */ - public SeqStep addStep(OPTYPE type) { - SeqStep newStep = new SeqStep(type); - mSequence.add(newStep); - return newStep; - } - - /** - * Add a sub-step to a sequencer step. All requests added to the same index will be send to - * the SapServer in the order added before listening for the response. - * The response order is not validated - hence for each response received the entire list of - * responses in the step will be searched for a match. - * @param index the index returned from addStep() to which the sub-step is to be added. - * @param request The request to send to the SAP server - * @param response The response to EXPECT from the SAP server - - public void addSubStep(int index, SapMessage request, SapMessage response) { - SeqStep step = sequence.get(index); - step.add(request, response); - }*/ - - - /** - * Run the sequence. - * Validate the response is either the expected response or one of the expected events. - * - * @return true when done - asserts at error/fail - */ - public boolean run() throws IOException { - CountDownLatch stopLatch = new CountDownLatch(1); - - /* TODO: - * First create sequencer to validate using BT-snoop - * 1) Create the transports (this could include a validation sniffer on each side) - * 2) Create a server thread with a link to the transport - * 3) execute the client operation - * 4) validate response - * - * On server: - * 1) validate the request contains the expected content - * 2) send response. - * */ - - /* Create the server */ - if(mServerTransport != null) { - mServerSession = new ServerSession(mServerTransport, new ObexTestServer(mSequence, - stopLatch), null); - } - - /* Create the client */ - if(mClientTransport != null) { - mClientSession = new ClientSession(mClientTransport); - - for(SeqStep step : mSequence) { - long stepIndex = mSequence.indexOf(step); - - Log.i(TAG, "Executing step " + stepIndex + " of type: " + step.mType); - - switch(step.mType) { - case CONNECT: { - HeaderSet reqHeaders = step.mReqHeaders; - if(reqHeaders == null) { - reqHeaders = new HeaderSet(); - } - reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex); - HeaderSet response = mClientSession.connect(reqHeaders); - validateHeaderSet(response, step.mResHeaders); - break; - } - case GET:{ - HeaderSet reqHeaders = step.mReqHeaders; - if(reqHeaders == null) { - reqHeaders = new HeaderSet(); - } - reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex); - Operation op = mClientSession.get(reqHeaders); - if(op != null) { - op.noBodyHeader(); - mDataHandler.readData(op.openDataInputStream(), step.mParams); - int responseCode = op.getResponseCode(); - Log.i(TAG, "response code: " + responseCode); - HeaderSet response = op.getReceivedHeader(); - validateHeaderSet(response, step.mResHeaders); - op.close(); - } - break; - } - case PUT: { - HeaderSet reqHeaders = step.mReqHeaders; - if(reqHeaders == null) { - reqHeaders = new HeaderSet(); - } - reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex); - Operation op = mClientSession.put(reqHeaders); - if(op != null) { - mDataHandler.writeData(op.openDataOutputStream(), step.mParams); - int responseCode = op.getResponseCode(); - Log.i(TAG, "response code: " + responseCode); - HeaderSet response = op.getReceivedHeader(); - validateHeaderSet(response, step.mResHeaders); - op.close(); - } - break; - } - case DISCONNECT: { - Log.i(TAG,"Requesting disconnect..."); - HeaderSet reqHeaders = step.mReqHeaders; - if(reqHeaders == null) { - reqHeaders = new HeaderSet(); - } - reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex); - try{ - HeaderSet response = mClientSession.disconnect(reqHeaders); - Log.i(TAG,"Received disconnect response..."); - // For some reason this returns -1 -> EOS - // Maybe increase server timeout. - validateHeaderSet(response, step.mResHeaders); - } catch (IOException e) { - Log.e(TAG, "Error getting response code", e); - } - break; - } - default: - assertTrue("Unknown type: " + step.mType, false); - break; - - } - } - mClientSession.close(); - } - /* All done, close down... */ - if(mServerSession != null) { - boolean interrupted = false; - do { - try { - interrupted = false; - Log.i(TAG,"Waiting for stopLatch signal..."); - stopLatch.await(); - } catch (InterruptedException e) { - Log.w(TAG,e); - interrupted = true; - } - } while (interrupted == true); - Log.i(TAG,"stopLatch signal received closing down..."); - try { - interrupted = false; - Log.i(TAG," Sleep 50ms to allow disconnect signal to be send before closing."); - Thread.sleep(50); - } catch (InterruptedException e) { - Log.w(TAG,e); - interrupted = true; - } - mServerSession.close(); - } - // this will close the I/O streams as well. - return true; - } - - public void shutdown() { -// mMessageHandler.removeCallbacksAndMessages(null); -// mMessageHandler.quit(); -// mMessageHandler = null; - } - - - void validateHeaderSet(HeaderSet headers, HeaderSet expected) throws IOException { - /* TODO: Implement and assert if different */ - if(headers.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) { - Log.e(TAG,"Wrong ResponseCode: " + headers.getResponseCode()); - assertTrue(false); - } - } - -// private void startTimer() { -// Message timeoutMessage = mMessageHandler.obtainMessage(MSG_ID_TIMEOUT); -// mMessageHandler.sendMessageDelayed(timeoutMessage, TIMEOUT_VALUE); -// } -// -// private void stopTimer() { -// mMessageHandler.removeMessages(MSG_ID_TIMEOUT); -// } - - @Override - public boolean handleMessage(Message msg) { - Log.i(TAG,"Handling message ID: " + msg.what); - switch(msg.what) { - case MSG_ID_TIMEOUT: - Log.w(TAG, "Timeout occured!"); -/* try { - //inStream.close(); - } catch (IOException e) { - Log.e(TAG, "failed to close inStream", e); - } - try { - //outStream.close(); - } catch (IOException e) { - Log.e(TAG, "failed to close outStream", e); - }*/ - break; - default: - /* Message not handled */ - return false; - } - return true; // Message handles - } - - - - } +} -}
\ No newline at end of file diff --git a/tests/src/com/android/bluetooth/tests/ObexTestServer.java b/tests/src/com/android/bluetooth/tests/ObexTestServer.java index b7df65c57..1efe56d5c 100644 --- a/tests/src/com/android/bluetooth/tests/ObexTestServer.java +++ b/tests/src/com/android/bluetooth/tests/ObexTestServer.java @@ -13,8 +13,6 @@ import javax.obex.ServerRequestHandler; import android.util.Log; -import com.android.bluetooth.tests.ObexTest.TestSequencer.SeqStep; - public class ObexTestServer extends ServerRequestHandler { private static final String TAG = "ObexTestServer"; @@ -40,7 +38,7 @@ public class ObexTestServer extends ServerRequestHandler { int index; int result = ResponseCodes.OBEX_HTTP_OK; try { - index = ((Long)request.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue(); + index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue(); mOperationIndex = index; } catch (IOException e) { Log.e(TAG, "Exception in onConnect - aborting..."); @@ -58,7 +56,7 @@ public class ObexTestServer extends ServerRequestHandler { int index; int result = ResponseCodes.OBEX_HTTP_OK; try { - index = ((Long)request.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue(); + index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue(); mOperationIndex = index; } catch (IOException e) { Log.e(TAG, "Exception in onDisconnect..."); @@ -89,7 +87,7 @@ public class ObexTestServer extends ServerRequestHandler { try{ inStream = operation.openInputStream(); HeaderSet reqHeaders = operation.getReceivedHeader(); - int index = ((Long)reqHeaders.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue(); + int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue(); mOperationIndex = index; mDataHandler.readData(inStream, mSequence.get(index).mParams); } catch (IOException e) { @@ -121,7 +119,7 @@ public class ObexTestServer extends ServerRequestHandler { try{ outStream = operation.openOutputStream(); HeaderSet reqHeaders = operation.getReceivedHeader(); - int index = ((Long)reqHeaders.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue(); + int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue(); mOperationIndex = index; mDataHandler.writeData(outStream, mSequence.get(index).mParams); } catch (IOException e) { diff --git a/tests/src/com/android/bluetooth/tests/SdpManagerTest.java b/tests/src/com/android/bluetooth/tests/SdpManagerTest.java index c72207298..2b7310d1d 100644 --- a/tests/src/com/android/bluetooth/tests/SdpManagerTest.java +++ b/tests/src/com/android/bluetooth/tests/SdpManagerTest.java @@ -34,8 +34,8 @@ public class SdpManagerTest extends AndroidTestCase { public static final int SDP_RECORD_COUNT = 12; /* Maximum number of records to create */ public static final int SDP_ITERATIONS = 2000; - public static final String SDP_SERVER_NAME = "SDP test Server"; - public static final String SDP_CLIENT_NAME = "SDP test Client"; + public static final String SDP_SERVER_NAME = "SDP test server"; + public static final String SDP_CLIENT_NAME = "SDP test client"; public static final long SDP_FEATURES = 0x87654321L; /* 32 bit */ public static final int SDP_MSG_TYPES = 0xf1; /* 8 bit */ @@ -51,7 +51,7 @@ public class SdpManagerTest extends AndroidTestCase { Log.e(TAG,"No Bluetooth Device!"); assertTrue(false); } - ObexTest.enableBt(bt); + BluetoothTestUtils.enableBt(bt); mManager = SdpManager.getDefaultManager(); addRemoveRecords(SDP_RECORD_COUNT); } @@ -62,7 +62,7 @@ public class SdpManagerTest extends AndroidTestCase { Log.e(TAG,"No Bluetooth Device!"); assertTrue(false); } - ObexTest.enableBt(bt); + BluetoothTestUtils.enableBt(bt); mManager = SdpManager.getDefaultManager(); int handles[] = new int[SDP_RECORD_COUNT]; @@ -175,7 +175,7 @@ public class SdpManagerTest extends AndroidTestCase { mClientSession = new ClientSession(clientTransport); { // Connect HeaderSet reqHeaders = new HeaderSet(); - reqHeaders.setHeader(ObexTest.STEP_INDEX_HEADER, (long)0); + reqHeaders.setHeader(TestSequencer.STEP_INDEX_HEADER, (long)0); HeaderSet response = mClientSession.connect(reqHeaders); assertEquals(response.responseCode, ResponseCodes.OBEX_HTTP_OK); } @@ -186,7 +186,7 @@ public class SdpManagerTest extends AndroidTestCase { { // get operation to trigger SDP search on peer device HeaderSet reqHeaders = new HeaderSet(); - reqHeaders.setHeader(ObexTest.STEP_INDEX_HEADER, (long)iteration); + reqHeaders.setHeader(TestSequencer.STEP_INDEX_HEADER, (long)iteration); reqHeaders.setHeader(HeaderSet.COUNT, (long)count); reqHeaders.setHeader(HeaderSet.NAME, uuids_str); Operation op = mClientSession.get(reqHeaders); @@ -201,7 +201,7 @@ public class SdpManagerTest extends AndroidTestCase { } { // disconnect to end test HeaderSet reqHeaders = new HeaderSet(); - reqHeaders.setHeader(ObexTest.STEP_INDEX_HEADER, 0L); // signals end of test + reqHeaders.setHeader(TestSequencer.STEP_INDEX_HEADER, 0L); // signals end of test HeaderSet response = mClientSession.disconnect(reqHeaders); assertEquals(response.responseCode, ResponseCodes.OBEX_HTTP_OK); } diff --git a/tests/src/com/android/bluetooth/tests/SdpManagerTestServer.java b/tests/src/com/android/bluetooth/tests/SdpManagerTestServer.java index 2151e564b..19e01bea4 100644 --- a/tests/src/com/android/bluetooth/tests/SdpManagerTestServer.java +++ b/tests/src/com/android/bluetooth/tests/SdpManagerTestServer.java @@ -1,9 +1,6 @@ package com.android.bluetooth.tests; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.CountDownLatch; @@ -13,9 +10,12 @@ import javax.obex.ResponseCodes; import javax.obex.ServerRequestHandler; import junit.framework.Assert; - import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothUuid; +import android.bluetooth.SdpMasRecord; +import android.bluetooth.SdpMnsRecord; +import android.bluetooth.SdpOppOpsRecord; +import android.bluetooth.SdpPseRecord; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -25,11 +25,6 @@ import android.util.Log; import com.android.bluetooth.btservice.AbstractionLayer; import com.android.bluetooth.sdp.SdpManager; -import com.android.bluetooth.sdp.SdpMasRecord; -import com.android.bluetooth.sdp.SdpMnsRecord; -import com.android.bluetooth.sdp.SdpOppOpsRecord; -import com.android.bluetooth.sdp.SdpPseRecord; -import com.android.bluetooth.tests.ObexTest.TestSequencer.SeqStep; /** * We use an OBEX server to execute SDP search operations, and validate results. @@ -64,7 +59,7 @@ public class SdpManagerTestServer extends ServerRequestHandler { int index; int result = ResponseCodes.OBEX_HTTP_OK; try { - index = ((Long)request.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue(); + index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue(); mOperationIndex = index; } catch (IOException e) { Log.e(TAG, "Exception in onConnect - aborting..."); @@ -80,7 +75,7 @@ public class SdpManagerTestServer extends ServerRequestHandler { int index; int result = ResponseCodes.OBEX_HTTP_OK; try { - index = ((Long)request.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue(); + index = ((Long)request.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue(); mOperationIndex = index; } catch (IOException e) { Log.e(TAG, "Exception in onDisconnect..."); @@ -122,7 +117,7 @@ public class SdpManagerTestServer extends ServerRequestHandler { mResult = ResponseCodes.OBEX_HTTP_OK; try{ HeaderSet reqHeaders = operation.getReceivedHeader(); - int index = ((Long)reqHeaders.getHeader(ObexTest.STEP_INDEX_HEADER)).intValue(); + int index = ((Long)reqHeaders.getHeader(TestSequencer.STEP_INDEX_HEADER)).intValue(); mOperationIndex = index; /* Get the expected number of records to read. */ int count = ((Long)reqHeaders.getHeader(HeaderSet.COUNT)).intValue(); diff --git a/tests/src/com/android/bluetooth/tests/SeqStep.java b/tests/src/com/android/bluetooth/tests/SeqStep.java new file mode 100644 index 000000000..3f6772a5c --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/SeqStep.java @@ -0,0 +1,88 @@ +package com.android.bluetooth.tests; + +import java.io.IOException; +import java.util.ArrayList; + +import javax.obex.HeaderSet; +import javax.obex.ObexPacket; +import javax.obex.Operation; + +import junit.framework.Assert; + +import com.android.bluetooth.tests.TestSequencer.OPTYPE; + +public class SeqStep { + /** + * Test step class to define the operations to be tested. + * Some of the data in these test steps will be modified during + * test - e.g. the HeaderSets will be modified to enable SRM + * and/or carry test information + */ + /* Operation type - Connect, Get, Put etc. */ + public OPTYPE mType; + /* The headers to send in the request - and validate on server side */ + public HeaderSet mReqHeaders = null; + /* The headers to send in the response - and validate on client side */ + public HeaderSet mResHeaders = null; + /* Use SRM */ + public boolean mUseSrm = false; + /* The amount of data to include in the body */ + public ObexTestParams mParams = null; + /* The offset into the data where the un-pause signal is to be sent */ + public int mUnPauseOffset = -1; + /* The offset into the data where the Abort request is to be sent */ + public int mAbortOffset = -1; + /* The side to perform Abort */ + public boolean mServerSideAbout = false; + /* The ID of the test step */ + private int mId; + + public boolean mSetPathBackup = false; /* bit 0 in flags */ + public boolean mSetPathCreate = false; /* Inverse of bit 1 in flags */ + + + public ISeqStepValidator mValidator = null; + public ISeqStepAction mServerPreAction = null; + public ISeqStepAction mClientPostAction = null; + + /* Arrays to hold expected sequence of request/response packets. */ + public ArrayList<ObexPacket> mRequestPackets = null; + public ArrayList<ObexPacket> mResponsePackets = null; + + public int index = 0; /* requests with same index are executed in parallel + (without waiting for a response) */ + + public SeqStep(OPTYPE type) { + mRequestPackets = new ArrayList<ObexPacket>(); + mResponsePackets = new ArrayList<ObexPacket>(); + mType = type; + } + + public boolean validate(HeaderSet response, Operation op) throws IOException { + Assert.assertNotNull(mValidator); + return mValidator.validate(this, response, op); + } + + public void serverPreAction(HeaderSet request, Operation op) throws IOException { + if(mServerPreAction != null) { + mServerPreAction.execute(this, request, op); + } + } + + public void clientPostAction(HeaderSet response, Operation op) throws IOException { + if(mClientPostAction != null) { + mClientPostAction.execute(this, response, op); + } + } + + + /* TODO: Consider to build these automatically based on the operations + * to be performed. Validate using utility functions - not strict + * binary compare. + * + * OR simply remove!*/ + public void addObexPacketSet(ObexPacket request, ObexPacket response) { + mRequestPackets.add(request); + mResponsePackets.add(response); + } +} diff --git a/tests/src/com/android/bluetooth/tests/TestSequencer.java b/tests/src/com/android/bluetooth/tests/TestSequencer.java new file mode 100644 index 000000000..fb0dcbae8 --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/TestSequencer.java @@ -0,0 +1,284 @@ +package com.android.bluetooth.tests; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; + +import javax.obex.ClientSession; +import javax.obex.HeaderSet; +import javax.obex.ObexTransport; +import javax.obex.Operation; +import javax.obex.ServerSession; + +import junit.framework.Assert; + +import android.content.Context; +import android.hardware.camera2.impl.GetCommand; +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.HandlerThread; +import android.os.Message; +import android.os.PowerManager; +import android.util.Log; + +public class TestSequencer implements Callback { + protected static String TAG = "TestSequencer"; + protected static final boolean D = true; + + private final static int MSG_ID_TIMEOUT = 0x01; + private final static int TIMEOUT_VALUE = 100*2000; // ms + private ArrayList<SeqStep> mSequence = null; + private HandlerThread mHandlerThread = null; + private Handler mMessageHandler = null; + private ObexTransport mClientTransport; + private ObexTransport mServerTransport; + + private ClientSession mClientSession = null; + private ServerSession mServerSession = null; + public static final int STEP_INDEX_HEADER = 0xF1; /*0xFE*/ + + public enum OPTYPE {CONNECT, PUT, GET, SET_PATH, DISCONNECT}; + + private ITestSequenceConfigurator mConfigurator = null; + + public TestSequencer(ObexTransport clientTransport, ObexTransport serverTransport, + ITestSequenceConfigurator configurator) + throws IOException { + /* Setup the looper thread to handle timeout messages */ +// mHandlerThread = new HandlerThread("TestTimeoutHandler", +// android.os.Process.THREAD_PRIORITY_BACKGROUND); +// mHandlerThread.start(); +// Looper testLooper = mHandlerThread.getLooper(); +// mMessageHandler = new Handler(testLooper, this); + //TODO: fix looper cleanup on server - crash after 464 iterations - related to prepare? + + mClientTransport = clientTransport; + mServerTransport = serverTransport; + + /* Initialize members */ + mSequence = new ArrayList<SeqStep>(); + mConfigurator = configurator; + Assert.assertNotNull(configurator); + } + + /** + * Add a test step to the sequencer. + * @param type the OBEX operation to perform. + * @return the created step, which can be decorated before execution. + */ + public SeqStep addStep(OPTYPE type, ISeqStepValidator validator) { + SeqStep newStep = new SeqStep(type); + newStep.mValidator = validator; + mSequence.add(newStep); + return newStep; + } + + /** + * Add a sub-step to a sequencer step. All requests added to the same index will be send to + * the SapServer in the order added before listening for the response. + * The response order is not validated - hence for each response received the entire list of + * responses in the step will be searched for a match. + * @param index the index returned from addStep() to which the sub-step is to be added. + * @param request The request to send to the SAP server + * @param response The response to EXPECT from the SAP server + + public void addSubStep(int index, SapMessage request, SapMessage response) { + SeqStep step = sequence.get(index); + step.add(request, response); + }*/ + + + /** + * Run the sequence. + * Validate the response is either the expected response or one of the expected events. + * + * @return true when done - asserts at error/fail + */ + public boolean run(Context context) throws IOException { + CountDownLatch stopLatch = new CountDownLatch(1); + PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + //wl.acquire(); + try { + /* TODO: + * First create sequencer to validate using BT-snoop + * 1) Create the transports (this could include a validation sniffer on each side) + * 2) Create a server thread with a link to the transport + * 3) execute the client operation + * 4) validate response + * + * On server: + * 1) validate the request contains the expected content + * 2) send response. + * */ + + /* Create the server */ + if(mServerTransport != null) { + mServerSession = new ServerSession(mServerTransport, + mConfigurator.getObexServer(mSequence, stopLatch) , null); + } + + /* Create the client */ + if(mClientTransport != null) { + mClientSession = new ClientSession(mClientTransport); + + for(SeqStep step : mSequence) { + long stepIndex = mSequence.indexOf(step); + + Log.i(TAG, "Executing step " + stepIndex + " of type: " + step.mType); + + switch(step.mType) { + case CONNECT: { + HeaderSet reqHeaders = step.mReqHeaders; + if(reqHeaders == null) { + reqHeaders = new HeaderSet(); + } + reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex); + HeaderSet response = mClientSession.connect(reqHeaders); + step.validate(response, null); + step.clientPostAction(response, null); + break; + } + case GET:{ + HeaderSet reqHeaders = step.mReqHeaders; + if(reqHeaders == null) { + reqHeaders = new HeaderSet(); + } + reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex); + Log.i(TAG, " Starting operation..."); + Operation op = mClientSession.get(reqHeaders); + Log.i(TAG, " Operation done..."); + step.validate(null, op); + step.clientPostAction(null, op); + break; + } + case PUT: { + HeaderSet reqHeaders = step.mReqHeaders; + if(reqHeaders == null) { + reqHeaders = new HeaderSet(); + } + reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex); + Operation op = mClientSession.put(reqHeaders); + step.validate(null, op); + step.clientPostAction(null, op); + break; + } + case SET_PATH: { + HeaderSet reqHeaders = step.mReqHeaders; + if(reqHeaders == null) { + reqHeaders = new HeaderSet(); + } + reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex); + try{ + HeaderSet response = mClientSession.setPath(reqHeaders, + step.mSetPathBackup, step.mSetPathCreate);; + Log.i(TAG,"Received setPath response..."); + step.validate(response, null); + step.clientPostAction(response, null); + } catch (IOException e) { + Log.e(TAG, "Error getting response code", e); + } + break; + } + case DISCONNECT: { + Log.i(TAG,"Requesting disconnect..."); + HeaderSet reqHeaders = step.mReqHeaders; + if(reqHeaders == null) { + reqHeaders = new HeaderSet(); + } + reqHeaders.setHeader(STEP_INDEX_HEADER, stepIndex); + try{ + HeaderSet response = mClientSession.disconnect(reqHeaders); + Log.i(TAG,"Received disconnect response..."); + step.validate(response, null); + step.clientPostAction(response, null); + } catch (IOException e) { + Log.e(TAG, "Error getting response code", e); + } + break; + } + default: + Assert.assertTrue("Unknown type: " + step.mType, false); + break; + + } + } + mClientSession.close(); + } + /* All done, close down... */ + if(mServerSession != null) { + boolean interrupted = false; + do { + try { + interrupted = false; + Log.i(TAG,"Waiting for stopLatch signal..."); + stopLatch.await(); + } catch (InterruptedException e) { + Log.w(TAG,e); + interrupted = true; + } + } while (interrupted == true); + Log.i(TAG,"stopLatch signal received closing down..."); + try { + interrupted = false; + Log.i(TAG," Sleep 50ms to allow disconnect signal to be send before closing."); + Thread.sleep(50); + } catch (InterruptedException e) { + Log.w(TAG,e); + interrupted = true; + } + mServerSession.close(); + } + // this will close the I/O streams as well. + } finally { + //wl.release(); + } + return true; + } + + public void shutdown() { +// mMessageHandler.removeCallbacksAndMessages(null); +// mMessageHandler.quit(); +// mMessageHandler = null; + } + + +// private void startTimer() { +// Message timeoutMessage = mMessageHandler.obtainMessage(MSG_ID_TIMEOUT); +// mMessageHandler.sendMessageDelayed(timeoutMessage, TIMEOUT_VALUE); +// } +// +// private void stopTimer() { +// mMessageHandler.removeMessages(MSG_ID_TIMEOUT); +// } + + @Override + public boolean handleMessage(Message msg) { + + Log.i(TAG,"Handling message ID: " + msg.what); + + switch(msg.what) { + case MSG_ID_TIMEOUT: + Log.w(TAG, "Timeout occured!"); +/* try { + //inStream.close(); + } catch (IOException e) { + Log.e(TAG, "failed to close inStream", e); + } + try { + //outStream.close(); + } catch (IOException e) { + Log.e(TAG, "failed to close outStream", e); + }*/ + break; + default: + /* Message not handled */ + return false; + } + return true; // Message handles + } + + + +} + |