diff options
author | Kim Schulz <k.schulz@samsung.com> | 2013-08-22 10:57:59 +0200 |
---|---|---|
committer | Zhihai Xu <zhihaixu@google.com> | 2013-09-13 15:41:35 -0700 |
commit | 70be005a18a35ec5fcb46152f0dfbe82156efa3a (patch) | |
tree | cf122cc80b0a1f6c3b583507c163bf3f8aef2741 | |
parent | cd34ad74f093c4867e616ba247fe3853b06afebc (diff) | |
download | android_packages_apps_Bluetooth-70be005a18a35ec5fcb46152f0dfbe82156efa3a.tar.gz android_packages_apps_Bluetooth-70be005a18a35ec5fcb46152f0dfbe82156efa3a.tar.bz2 android_packages_apps_Bluetooth-70be005a18a35ec5fcb46152f0dfbe82156efa3a.zip |
Fixes to the google review comments + spec 1.1
- updated code to comply with MAP spec 1.1
- removed activity + strings.xml
- removed unused notification code
- fixed TODOs
- added more string validation and case insensitivity
- fixed internal+google review comments
- Added dump of incoming bMessages to /sdcard/bluetooth/log when verbose debug in enabled. Only the latest received message will be stored
- Fix functions msgListingSize and msgListingHasUnread to also consider mms filter message type
- Fix wrong tag length define for notification status parameter
- Re-added shutdown code to interupt the MNS
- removed map activity
- Added initial bluetooth map unit tests
- Fix map event report xml start tag to uppercase 'MAP'
- added support for using ProfileService class
- changed the way the Broadcast Receiver was implemented
- Fixed minor bugs found during Automotive Test Event
- FilterPeriod application parameters can be present, but with zero length
- For MMS the end-boundary were added too early
- The FOLDER entry in bMessage can be empty for a message push
- Wrong error value returned for a set status operation with a wrong handle
- In getMessage() exclude all binary content and smil.xml if the appParam attachment is set to "no".
- Set correct content id and content location for mms. Fix mms mime parser bug.
- moved disconnect to Handler thread
- fixed multipart-message split bug.
- added a few Unit tests for multi-part messages
- MMS parser optimized
- fixed exception in MNS obex Client
- fixed problem with Native PDUs not getting correct timestamp
- corrected mixup in ordinator/recipient for MMS
Change-Id: I3875762822a7f8ce0132065e0da5d0257e3850a1
Bug:10692365
24 files changed, 2018 insertions, 1180 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 017bd88c0..9f06ffd49 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -52,11 +52,11 @@ <uses-permission android:name="com.android.gallery3d.permission.GALLERY_PROVIDER"/> <uses-permission android:name="android.permission.MMS_SEND_OUTBOX_MSG"/> <uses-permission android:name="android.permission.RECEIVE_SMS" /> - <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.READ_SMS" /> <uses-permission android:name="android.permission.WRITE_SMS" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> + <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- For PBAP Owner Vcard Info --> <uses-permission android:name="android.permission.READ_PROFILE"/> @@ -227,15 +227,6 @@ <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver> - <activity android:name=".map.BluetoothMapActivity" - android:process="@string/process" - android:excludeFromRecents="true" - android:theme="@*android:style/Theme.Holo.Dialog.Alert" - android:enabled="@bool/profile_supported_map"> - <intent-filter> - <category android:name="android.intent.category.DEFAULT" /> - </intent-filter> - </activity> <service android:process="@string/process" android:name=".map.BluetoothMapService" @@ -244,17 +235,6 @@ <action android:name="android.bluetooth.IBluetoothMap" /> </intent-filter> </service> - <receiver - android:process="@string/process" - android:exported="true" - android:name=".map.BluetoothMapReceiver" - android:enabled="@bool/profile_supported_map"> - <intent-filter> - <action android:name="android.bluetooth.adapter.action.STATE_CHANGED"/> - <action android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REPLY" /> - <category android:name="android.intent.category.DEFAULT" /> - </intent-filter> - </receiver> <service android:process="@string/process" android:name = ".gatt.GattService" diff --git a/res/values/strings_map.xml b/res/values/strings_map.xml deleted file mode 100644 index ba30b9969..000000000 --- a/res/values/strings_map.xml +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="map_session_key_dialog_title">Type session key for %1$s</string> - <string name="map_session_key_dialog_header">Bluetooth session key required</string> - <string name="map_acceptance_timeout_message">There was time out to accept connection with %1$s</string> - <string name="map_authentication_timeout_message">There was time out to input session key with %1$s</string> - <string name="map_auth_notif_ticker">Obex authentication request</string> - <!-- Notification title when a Bluetooth device wants to pair with us --> - <string name="map_auth_notif_title">Session Key</string> - <!-- Notification message when a Bluetooth device wants to pair with us --> - <string name="map_auth_notif_message">Type session key for %1$s</string> - <string name="map_defaultname">Carkit</string> - <string name="map_unknownName">Unknown name</string> - <string name="map_localPhoneName">My name</string> - <string name="map_defaultnumber">000000</string> -</resources> diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java index a2b2ff393..99cc411a9 100644 --- a/src/com/android/bluetooth/btservice/Config.java +++ b/src/com/android/bluetooth/btservice/Config.java @@ -29,6 +29,7 @@ import com.android.bluetooth.hfp.HeadsetService; import com.android.bluetooth.hid.HidService; import com.android.bluetooth.pan.PanService; import com.android.bluetooth.gatt.GattService; +import com.android.bluetooth.map.BluetoothMapService; public class Config { private static final String TAG = "AdapterServiceConfig"; @@ -44,7 +45,8 @@ public class Config { HidService.class, HealthService.class, PanService.class, - GattService.class + GattService.class, + BluetoothMapService.class }; /** * Resource flag to indicate whether profile is supported or not. @@ -55,7 +57,8 @@ public class Config { R.bool.profile_supported_hid, R.bool.profile_supported_hdp, R.bool.profile_supported_pan, - R.bool.profile_supported_gatt + R.bool.profile_supported_gatt, + R.bool.profile_supported_map }; private static Class[] SUPPORTED_PROFILES = new Class[0]; diff --git a/src/com/android/bluetooth/hfp/HeadsetService.java b/src/com/android/bluetooth/hfp/HeadsetService.java index 6ea0721ac..d66009cf0 100755 --- a/src/com/android/bluetooth/hfp/HeadsetService.java +++ b/src/com/android/bluetooth/hfp/HeadsetService.java @@ -104,8 +104,12 @@ public class HeadsetService extends ProfileService { } } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { - Log.v(TAG, "HeadsetService - Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY"); - mStateMachine.handleAccessPermissionResult(intent); + int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, + BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); + if (requestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) { + Log.v(TAG, "Received BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY"); + mStateMachine.handleAccessPermissionResult(intent); + } } } }; diff --git a/src/com/android/bluetooth/map/BluetoothMapActivity.java b/src/com/android/bluetooth/map/BluetoothMapActivity.java deleted file mode 100644 index d415eef21..000000000 --- a/src/com/android/bluetooth/map/BluetoothMapActivity.java +++ /dev/null @@ -1,284 +0,0 @@ - -/* -* Copyright (C) 2013 Samsung System LSI -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -package com.android.bluetooth.map; - -import com.android.bluetooth.R; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.preference.Preference; -import android.util.Log; -import android.view.View; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Button; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.text.InputFilter; -import android.text.TextWatcher; -import android.text.InputFilter.LengthFilter; - -import com.android.internal.app.AlertActivity; -import com.android.internal.app.AlertController; - -/** - * MapActivity shows two dialogues: One for accepting incoming map request and - * the other prompts the user to enter a session key for authentication with a - * remote Bluetooth device. - */ -public class BluetoothMapActivity extends AlertActivity implements - DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener, TextWatcher { - private static final String TAG = "BluetoothMapActivity"; - - private static final boolean V = BluetoothMapService.VERBOSE; - - private static final int BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH = 16; - - private static final int DIALOG_YES_NO_AUTH = 1; - - private static final String KEY_USER_TIMEOUT = "user_timeout"; - - private View mView; - - private EditText mKeyView; - - private TextView messageView; - - private String mSessionKey = ""; - - private int mCurrentDialog; - - private Button mOkButton; - - private CheckBox mAlwaysAllowed; - - private boolean mTimeout = false; - - private boolean mAlwaysAllowedValue = true; - - private static final int DISMISS_TIMEOUT_DIALOG = 0; - - private static final int DISMISS_TIMEOUT_DIALOG_VALUE = 2000; - - private BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (!BluetoothMapService.USER_CONFIRM_TIMEOUT_ACTION.equals(intent.getAction())) { - return; - } - onTimeout(); - } - }; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - Intent i = getIntent(); - String action = i.getAction(); - if (action.equals(BluetoothMapService.AUTH_CHALL_ACTION)) { - showMapDialog(DIALOG_YES_NO_AUTH); - mCurrentDialog = DIALOG_YES_NO_AUTH; - } else { - Log.e(TAG, "Error: this activity may be started only with intent " - + "MAP_ACCESS_REQUEST or MAP_AUTH_CHALL "); - finish(); - } - registerReceiver(mReceiver, new IntentFilter( - BluetoothMapService.USER_CONFIRM_TIMEOUT_ACTION)); - } - - private void showMapDialog(int id) { - final AlertController.AlertParams p = mAlertParams; - switch (id) { - case DIALOG_YES_NO_AUTH: - p.mIconId = android.R.drawable.ic_dialog_info; - p.mTitle = getString(R.string.map_session_key_dialog_header); - p.mView = createView(DIALOG_YES_NO_AUTH); - p.mPositiveButtonText = getString(android.R.string.ok); - p.mPositiveButtonListener = this; - p.mNegativeButtonText = getString(android.R.string.cancel); - p.mNegativeButtonListener = this; - setupAlert(); - mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE); - mOkButton.setEnabled(false); - break; - default: - break; - } - } - - private String createDisplayText(final int id) { - String mRemoteName = BluetoothMapService.getRemoteDeviceName(); - switch (id) { - case DIALOG_YES_NO_AUTH: - String mMessage2 = getString(R.string.map_session_key_dialog_title, mRemoteName); - return mMessage2; - default: - return null; - } - } - - private View createView(final int id) { - switch (id) { - case DIALOG_YES_NO_AUTH: - mView = getLayoutInflater().inflate(R.layout.auth, null); - messageView = (TextView)mView.findViewById(R.id.message); - messageView.setText(createDisplayText(id)); - mKeyView = (EditText)mView.findViewById(R.id.text); - mKeyView.addTextChangedListener(this); - mKeyView.setFilters(new InputFilter[] { - new LengthFilter(BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH) - }); - return mView; - default: - return null; - } - } - - private void onPositive() { - if (!mTimeout) { - if (mCurrentDialog == DIALOG_YES_NO_AUTH) { - sendIntentToReceiver(BluetoothMapService.AUTH_RESPONSE_ACTION, - BluetoothMapService.EXTRA_SESSION_KEY, mSessionKey); - mKeyView.removeTextChangedListener(this); - } - } - mTimeout = false; - finish(); - } - - private void onNegative() { - if (mCurrentDialog == DIALOG_YES_NO_AUTH) { - sendIntentToReceiver(BluetoothMapService.AUTH_CANCELLED_ACTION, null, null); - mKeyView.removeTextChangedListener(this); - } - finish(); - } - - private void sendIntentToReceiver(final String intentName, final String extraName, - final String extraValue) { - Intent intent = new Intent(intentName); - intent.setClassName(BluetoothMapService.THIS_PACKAGE_NAME, BluetoothMapReceiver.class - .getName()); - if (extraName != null) { - intent.putExtra(extraName, extraValue); - } - sendBroadcast(intent); - } - - private void sendIntentToReceiver(final String intentName, final String extraName, - final boolean extraValue) { - Intent intent = new Intent(intentName); - intent.setClassName(BluetoothMapService.THIS_PACKAGE_NAME, BluetoothMapReceiver.class - .getName()); - if (extraName != null) { - intent.putExtra(extraName, extraValue); - } - sendBroadcast(intent); - } - - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - if (mCurrentDialog == DIALOG_YES_NO_AUTH) { - mSessionKey = mKeyView.getText().toString(); - } - onPositive(); - break; - - case DialogInterface.BUTTON_NEGATIVE: - onNegative(); - break; - default: - break; - } - } - - private void onTimeout() { - mTimeout = true; - if (mCurrentDialog == DIALOG_YES_NO_AUTH) { - messageView.setText(getString(R.string.map_authentication_timeout_message, - BluetoothMapService.getRemoteDeviceName())); - mKeyView.setVisibility(View.GONE); - mKeyView.clearFocus(); - mKeyView.removeTextChangedListener(this); - mOkButton.setEnabled(true); - mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE); - } - - mTimeoutHandler.sendMessageDelayed(mTimeoutHandler.obtainMessage(DISMISS_TIMEOUT_DIALOG), - DISMISS_TIMEOUT_DIALOG_VALUE); - } - - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - mTimeout = savedInstanceState.getBoolean(KEY_USER_TIMEOUT); - if (V) Log.v(TAG, "onRestoreInstanceState() mTimeout: " + mTimeout); - if (mTimeout) { - onTimeout(); - } - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(KEY_USER_TIMEOUT, mTimeout); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - unregisterReceiver(mReceiver); - } - - public boolean onPreferenceChange(Preference preference, Object newValue) { - return true; - } - - public void beforeTextChanged(CharSequence s, int start, int before, int after) { - } - - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - public void afterTextChanged(android.text.Editable s) { - if (s.length() > 0) { - mOkButton.setEnabled(true); - } - } - - private final Handler mTimeoutHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case DISMISS_TIMEOUT_DIALOG: - if (V) Log.v(TAG, "Received DISMISS_TIMEOUT_DIALOG msg."); - finish(); - break; - default: - break; - } - } - }; -} diff --git a/src/com/android/bluetooth/map/BluetoothMapAppParams.java b/src/com/android/bluetooth/map/BluetoothMapAppParams.java index e55c61185..ffdd2f1b1 100644 --- a/src/com/android/bluetooth/map/BluetoothMapAppParams.java +++ b/src/com/android/bluetooth/map/BluetoothMapAppParams.java @@ -54,7 +54,7 @@ public class BluetoothMapAppParams { private static final int NEW_MESSAGE = 0x0D; private static final int NEW_MESSAGE_LEN = 0x01; //, 0x0000, 0x0001), private static final int NOTIFICATION_STATUS = 0x0E; - private static final int NOTIFICATION_STATUS_LEN = 0x02; //, 0x0000, 0xFFFF), + private static final int NOTIFICATION_STATUS_LEN = 0x01; //, 0x0000, 0xFFFF), private static final int MAS_INSTANCE_ID = 0x0F; private static final int MAS_INSTANCE_ID_LEN = 0x01; //, 0x0000, 0x00FF), private static final int PARAMETER_MASK = 0x10; @@ -183,15 +183,29 @@ public class BluetoothMapAppParams { setStartOffset(appParamBuf.getShort(i) & 0xffff); // Make it unsigned break; case FILTER_MESSAGE_TYPE: - setFilterMessageType(appParams[i] & 0x0f); + if (tagLength != FILTER_MESSAGE_TYPE_LEN) { + Log.w(TAG, "FILTER_MESSAGE_TYPE: Wrong length received: " + tagLength + " expected: " + + FILTER_MESSAGE_TYPE_LEN); + break; + } + setFilterMessageType(appParams[i] & 0x0f); break; case FILTER_PERIOD_BEGIN: - setFilterPeriodBegin(new String(appParams, i, tagLength)); + if(tagLength != 0) { + setFilterPeriodBegin(new String(appParams, i, tagLength)); + } break; case FILTER_PERIOD_END: - setFilterPeriodEnd(new String(appParams, i, tagLength)); + if(tagLength != 0) { + setFilterPeriodEnd(new String(appParams, i, tagLength)); + } break; case FILTER_READ_STATUS: + if (tagLength != FILTER_READ_STATUS_LEN) { + Log.w(TAG, "FILTER_READ_STATUS: Wrong length received: " + tagLength + " expected: " + + FILTER_READ_STATUS_LEN); + break; + } setFilterReadStatus(appParams[i] & 0x03); // Lower two bits break; case FILTER_RECIPIENT: @@ -201,51 +215,131 @@ public class BluetoothMapAppParams { setFilterOriginator(new String(appParams, i, tagLength)); break; case FILTER_PRIORITY: + if (tagLength != FILTER_PRIORITY_LEN) { + Log.w(TAG, "FILTER_PRIORITY: Wrong length received: " + tagLength + " expected: " + + FILTER_PRIORITY_LEN); + break; + } setFilterPriority(appParams[i] & 0x03); // Lower two bits break; case ATTACHMENT: + if (tagLength != ATTACHMENT_LEN) { + Log.w(TAG, "ATTACHMENT: Wrong length received: " + tagLength + " expected: " + + ATTACHMENT_LEN); + break; + } setAttachment(appParams[i] & 0x01); // Lower bit break; case TRANSPARENT: + if (tagLength != TRANSPARENT_LEN) { + Log.w(TAG, "TRANSPARENT: Wrong length received: " + tagLength + " expected: " + + TRANSPARENT_LEN); + break; + } setTransparent(appParams[i] & 0x01); // Lower bit break; case RETRY: + if (tagLength != RETRY_LEN) { + Log.w(TAG, "RETRY: Wrong length received: " + tagLength + " expected: " + + RETRY_LEN); + break; + } setRetry(appParams[i] & 0x01); // Lower bit break; case NEW_MESSAGE: + if (tagLength != NEW_MESSAGE_LEN) { + Log.w(TAG, "NEW_MESSAGE: Wrong length received: " + tagLength + " expected: " + + NEW_MESSAGE_LEN); + break; + } setNewMessage(appParams[i] & 0x01); // Lower bit break; case NOTIFICATION_STATUS: + if (tagLength != NOTIFICATION_STATUS_LEN) { + Log.w(TAG, "NOTIFICATION_STATUS: Wrong length received: " + tagLength + " expected: " + + NOTIFICATION_STATUS_LEN); + break; + } setNotificationStatus(appParams[i] & 0x01); // Lower bit break; case MAS_INSTANCE_ID: + if (tagLength != MAS_INSTANCE_ID_LEN) { + Log.w(TAG, "MAS_INSTANCE_ID: Wrong length received: " + tagLength + " expected: " + + MAS_INSTANCE_ID_LEN); + break; + } setMasInstanceId(appParams[i] & 0xff); break; case PARAMETER_MASK: + if (tagLength != PARAMETER_MASK_LEN) { + Log.w(TAG, "PARAMETER_MASK: Wrong length received: " + tagLength + " expected: " + + PARAMETER_MASK_LEN); + break; + } setParameterMask(appParamBuf.getInt(i) & 0xffffffffL); // Make it unsigned break; case FOLDER_LISTING_SIZE: + if (tagLength != FOLDER_LISTING_SIZE_LEN) { + Log.w(TAG, "FOLDER_LISTING_SIZE: Wrong length received: " + tagLength + " expected: " + + FOLDER_LISTING_SIZE_LEN); + break; + } setFolderListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned break; case MESSAGE_LISTING_SIZE: + if (tagLength != MESSAGE_LISTING_SIZE_LEN) { + Log.w(TAG, "MESSAGE_LISTING_SIZE: Wrong length received: " + tagLength + " expected: " + + MESSAGE_LISTING_SIZE_LEN); + break; + } setMessageListingSize(appParamBuf.getShort(i) & 0xffff); // Make it unsigned break; case SUBJECT_LENGTH: + if (tagLength != SUBJECT_LENGTH_LEN) { + Log.w(TAG, "SUBJECT_LENGTH: Wrong length received: " + tagLength + " expected: " + + SUBJECT_LENGTH_LEN); + break; + } setSubjectLength(appParams[i] & 0xff); break; case CHARSET: + if (tagLength != CHARSET_LEN) { + Log.w(TAG, "CHARSET: Wrong length received: " + tagLength + " expected: " + + CHARSET_LEN); + break; + } setCharset(appParams[i] & 0x01); // Lower bit break; case FRACTION_REQUEST: + if (tagLength != FRACTION_REQUEST_LEN) { + Log.w(TAG, "FRACTION_REQUEST: Wrong length received: " + tagLength + " expected: " + + FRACTION_REQUEST_LEN); + break; + } setFractionRequest(appParams[i] & 0x01); // Lower bit break; case FRACTION_DELIVER: + if (tagLength != FRACTION_DELIVER_LEN) { + Log.w(TAG, "FRACTION_DELIVER: Wrong length received: " + tagLength + " expected: " + + FRACTION_DELIVER_LEN); + break; + } setFractionDeliver(appParams[i] & 0x01); // Lower bit break; case STATUS_INDICATOR: + if (tagLength != STATUS_INDICATOR_LEN) { + Log.w(TAG, "STATUS_INDICATOR: Wrong length received: " + tagLength + " expected: " + + STATUS_INDICATOR_LEN); + break; + } setStatusIndicator(appParams[i] & 0x01); // Lower bit break; case STATUS_VALUE: + if (tagLength != STATUS_VALUE_LEN) { + Log.w(TAG, "STATUS_VALUER: Wrong length received: " + tagLength + " expected: " + + STATUS_VALUE_LEN); + break; + } setStatusValue(appParams[i] & 0x01); // Lower bit break; case MSE_TIME: diff --git a/src/com/android/bluetooth/map/BluetoothMapAuthenticator.java b/src/com/android/bluetooth/map/BluetoothMapAuthenticator.java index 12f64e063..2d345a133 100644 --- a/src/com/android/bluetooth/map/BluetoothMapAuthenticator.java +++ b/src/com/android/bluetooth/map/BluetoothMapAuthenticator.java @@ -64,7 +64,7 @@ public class BluetoothMapAuthenticator implements Authenticator { try { wait(); } catch (InterruptedException e) { - Log.e(TAG, "Interrupted while waiting on isChalled"); + Log.e(TAG, "Interrupted while waiting on isChallenged"); } } } diff --git a/src/com/android/bluetooth/map/BluetoothMapContent.java b/src/com/android/bluetooth/map/BluetoothMapContent.java index 531e90b2d..b75bc62f1 100644 --- a/src/com/android/bluetooth/map/BluetoothMapContent.java +++ b/src/com/android/bluetooth/map/BluetoothMapContent.java @@ -42,8 +42,8 @@ import com.google.android.mms.pdu.CharacterSets; public class BluetoothMapContent { private static final String TAG = "BluetoothMapContent"; - private static final boolean D = true; - private static final boolean V = true; + private static final boolean D = false; + private static final boolean V = false; private static final int MASK_SUBJECT = 0x1; private static final int MASK_DATETIME = 0x2; @@ -122,7 +122,7 @@ public class BluetoothMapContent { } private void addSmsEntry() { - Log.d(TAG, "*** Adding dummy sms ***"); + if (D) Log.d(TAG, "*** Adding dummy sms ***"); ContentValues mVal = new ContentValues(); mVal.put(Sms.ADDRESS, "1234"); @@ -208,11 +208,11 @@ public class BluetoothMapContent { String add = c.getString(c.getColumnIndex("address")); Integer type = c.getInt(c.getColumnIndex("type")); if (type == MMS_TO) { - Log.d(TAG, " recipient: " + add + " (type: " + type + ")"); + if (D) Log.d(TAG, " recipient: " + add + " (type: " + type + ")"); } else if (type == MMS_FROM) { - Log.d(TAG, " originator: " + add + " (type: " + type + ")"); + if (D) Log.d(TAG, " originator: " + add + " (type: " + type + ")"); } else { - Log.d(TAG, " address other: " + add + " (type: " + type + ")"); + if (D) Log.d(TAG, " address other: " + add + " (type: " + type + ")"); } } while(c.moveToNext()); @@ -232,7 +232,7 @@ public class BluetoothMapContent { while ((ch = is.read()) != -1) { sb.append((char)ch); } - Log.d(TAG, sb.toString()); + if (D) Log.d(TAG, sb.toString()); } catch (IOException e) { // do nothing for now @@ -251,7 +251,7 @@ public class BluetoothMapContent { selection, null, null); - Log.d(TAG, " parts:"); + if (D) Log.d(TAG, " parts:"); if (c.moveToFirst()) { do { Long partid = c.getLong(c.getColumnIndex(BaseColumns._ID)); @@ -261,14 +261,20 @@ public class BluetoothMapContent { String filename = c.getString(c.getColumnIndex("fn")); String text = c.getString(c.getColumnIndex("text")); Integer fd = c.getInt(c.getColumnIndex("_data")); + String cid = c.getString(c.getColumnIndex("cid")); + String cl = c.getString(c.getColumnIndex("cl")); + String cdisp = c.getString(c.getColumnIndex("cd")); - Log.d(TAG, " _id : " + partid + + if (D) Log.d(TAG, " _id : " + partid + "\n ct : " + ct + "\n partname : " + name + "\n charset : " + charset + "\n filename : " + filename + "\n text : " + text + - "\n fd : " + fd); + "\n fd : " + fd + + "\n cid : " + cid + + "\n cl : " + cl + + "\n cdisp : " + cdisp); /* if (ct.equals("image/jpeg")) { */ /* printMmsPartImage(partid); */ @@ -278,11 +284,11 @@ public class BluetoothMapContent { } public void dumpMmsTable() { - Log.d(TAG, "**** Dump of mms table ****"); + if (D) Log.d(TAG, "**** Dump of mms table ****"); Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, null, null, "_id DESC"); if (c != null) { - Log.d(TAG, "c.getCount() = " + c.getCount()); + if (D) Log.d(TAG, "c.getCount() = " + c.getCount()); c.moveToPosition(-1); while (c.moveToNext()) { printMms(c); @@ -298,11 +304,11 @@ public class BluetoothMapContent { public void dumpSmsTable() { addSmsEntry(); - Log.d(TAG, "**** Dump of sms table ****"); + if (D) Log.d(TAG, "**** Dump of sms table ****"); Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, null, null, "_id DESC"); if (c != null) { - Log.d(TAG, "c.getCount() = " + c.getCount()); + if (D) Log.d(TAG, "c.getCount() = " + c.getCount()); c.moveToPosition(-1); while (c.moveToNext()) { printSms(c); @@ -319,7 +325,7 @@ public class BluetoothMapContent { dumpMmsTable(); BluetoothMapAppParams ap = buildAppParams(); - Log.d(TAG, "message listing size = " + msgListingSize("inbox", ap)); + if (D) Log.d(TAG, "message listing size = " + msgListingSize("inbox", ap)); BluetoothMapMessageListing mList = msgListing("inbox", ap); try { mList.encode(); @@ -338,7 +344,7 @@ public class BluetoothMapContent { FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_PROTECTED) != 0) { String protect = "no"; - Log.d(TAG, "setProtected: " + protect); + if (D) Log.d(TAG, "setProtected: " + protect); e.setProtect(protect); } } @@ -358,45 +364,53 @@ public class BluetoothMapContent { } else { sent = "no"; } - Log.d(TAG, "setSent: " + sent); + if (D) Log.d(TAG, "setSent: " + sent); e.setSent(sent); } } private void setRead(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { - if ((ap.getParameterMask() & MASK_READ) != 0) { - int read = 0; - if (fi.msgType == FilterInfo.TYPE_SMS) { - read = c.getInt(c.getColumnIndex(Sms.READ)); - } else if (fi.msgType == FilterInfo.TYPE_MMS) { - read = c.getInt(c.getColumnIndex(Mms.READ)); - } - String setread = null; - if (read == 1) { - setread = "yes"; - } else { - setread = "no"; - } - Log.d(TAG, "setRead: " + setread); - e.setRead(setread); + int read = 0; + if (fi.msgType == FilterInfo.TYPE_SMS) { + read = c.getInt(c.getColumnIndex(Sms.READ)); + } else if (fi.msgType == FilterInfo.TYPE_MMS) { + read = c.getInt(c.getColumnIndex(Mms.READ)); + } + String setread = null; + if (read == 1) { + setread = "yes"; + } else { + setread = "no"; } + if (D) Log.d(TAG, "setRead: " + setread); + e.setRead(setread, ((ap.getParameterMask() & MASK_READ) != 0)); } private void setPriority(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_PRIORITY) != 0) { String priority = "no"; - Log.d(TAG, "setPriority: " + priority); + if (D) Log.d(TAG, "setPriority: " + priority); e.setPriority(priority); } } + /** + * For SMS we set the attachment size to 0, as all data will be text data, hence + * attachments for SMS is not possible. + * For MMS all data is actually attachments, hence we do set the attachment size to + * the total message size. To provide a more accurate attachment size, one could + * extract the length (in bytes) of the text parts. + */ private void setAttachmentSize(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_ATTACHMENT_SIZE) != 0) { int size = 0; - Log.d(TAG, "setAttachmentSize: " + size); + if (fi.msgType == FilterInfo.TYPE_MMS) { + size = c.getInt(c.getColumnIndex(Mms.MESSAGE_SIZE)); + } + if (D) Log.d(TAG, "setAttachmentSize: " + size); e.setAttachmentSize(size); } } @@ -421,7 +435,7 @@ public class BluetoothMapContent { } } } - Log.d(TAG, "setText: " + hasText); + if (D) Log.d(TAG, "setText: " + hasText); e.setText(hasText); } } @@ -430,7 +444,7 @@ public class BluetoothMapContent { FilterInfo fi, BluetoothMapAppParams ap) { if ((ap.getParameterMask() & MASK_RECEPTION_STATUS) != 0) { String status = "complete"; - Log.d(TAG, "setReceptionStatus: " + status); + if (D) Log.d(TAG, "setReceptionStatus: " + status); e.setReceptionStatus(status); } } @@ -445,7 +459,7 @@ public class BluetoothMapContent { } else if (fi.msgType == FilterInfo.TYPE_MMS) { size = c.getInt(c.getColumnIndex(Mms.MESSAGE_SIZE)); } - Log.d(TAG, "setSize: " + size); + if (D) Log.d(TAG, "setSize: " + size); e.setSize(size); } } @@ -463,7 +477,7 @@ public class BluetoothMapContent { } else if (fi.msgType == FilterInfo.TYPE_MMS) { type = TYPE.MMS; } - Log.d(TAG, "setType: " + type); + if (D) Log.d(TAG, "setType: " + type); e.setType(type); } } @@ -483,7 +497,7 @@ public class BluetoothMapContent { long id = c.getLong(c.getColumnIndex(BaseColumns._ID)); address = getAddressMms(mResolver, id, MMS_TO); } - Log.d(TAG, "setRecipientAddressing: " + address); + if (D) Log.d(TAG, "setRecipientAddressing: " + address); e.setRecipientAddressing(address); } } @@ -505,7 +519,7 @@ public class BluetoothMapContent { String phone = getAddressMms(mResolver, id, MMS_TO); name = getContactNameFromPhone(phone); } - Log.d(TAG, "setRecipientName: " + name); + if (D) Log.d(TAG, "setRecipientName: " + name); e.setRecipientName(name); } } @@ -525,7 +539,7 @@ public class BluetoothMapContent { long id = c.getLong(c.getColumnIndex(BaseColumns._ID)); address = getAddressMms(mResolver, id, MMS_FROM); } - Log.d(TAG, "setSenderAddressing: " + address); + if (D) Log.d(TAG, "setSenderAddressing: " + address); e.setSenderAddressing(address); } } @@ -547,7 +561,7 @@ public class BluetoothMapContent { String phone = getAddressMms(mResolver, id, MMS_FROM); name = getContactNameFromPhone(phone); } - Log.d(TAG, "setSenderName: " + name); + if (D) Log.d(TAG, "setSenderName: " + name); e.setSenderName(name); } } @@ -617,7 +631,7 @@ public class BluetoothMapContent { subject = subject.substring(0, Math.min(subject.length(), subLength)); } - Log.d(TAG, "setSubject: " + subject); + if (D) Log.d(TAG, "setSubject: " + subject); e.setSubject(subject); } } @@ -625,8 +639,18 @@ public class BluetoothMapContent { private void setHandle(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi, BluetoothMapAppParams ap) { long handle = c.getLong(c.getColumnIndex(BaseColumns._ID)); - Log.d(TAG, "setHandle: " + handle); - e.setHandle(handle); + TYPE type = null; + if (fi.msgType == FilterInfo.TYPE_SMS) { + if (fi.phoneType == TelephonyManager.PHONE_TYPE_GSM) { + type = TYPE.SMS_GSM; + } else if (fi.phoneType == TelephonyManager.PHONE_TYPE_CDMA) { + type = TYPE.SMS_CDMA; + } + } else if (fi.msgType == FilterInfo.TYPE_MMS) { + type = TYPE.MMS; + } + if (D) Log.d(TAG, "setHandle: " + handle + " - Type: " + type.name()); + e.setHandle(handle, type); } private BluetoothMapMessageListingElement element(Cursor c, FilterInfo fi, @@ -696,12 +720,12 @@ public class BluetoothMapContent { String phone = getAddressMms(mResolver, id, MMS_TO); if (phone != null && phone.length() > 0) { if (phone.matches(recip)) { - Log.d(TAG, "match recipient phone = " + phone); + if (D) Log.d(TAG, "match recipient phone = " + phone); res = true; } else { String name = getContactNameFromPhone(phone); if (name != null && name.length() > 0 && name.matches(recip)) { - Log.d(TAG, "match recipient name = " + name); + if (D) Log.d(TAG, "match recipient name = " + name); res = true; } else { res = false; @@ -720,10 +744,10 @@ public class BluetoothMapContent { String phone = fi.phoneNum; String name = fi.phoneAlphaTag; if (phone != null && phone.length() > 0 && phone.matches(recip)) { - Log.d(TAG, "match recipient phone = " + phone); + if (D) Log.d(TAG, "match recipient phone = " + phone); res = true; } else if (name != null && name.length() > 0 && name.matches(recip)) { - Log.d(TAG, "match recipient name = " + name); + if (D) Log.d(TAG, "match recipient name = " + name); res = true; } else { res = false; @@ -733,12 +757,12 @@ public class BluetoothMapContent { String phone = c.getString(c.getColumnIndex(Sms.ADDRESS)); if (phone != null && phone.length() > 0) { if (phone.matches(recip)) { - Log.d(TAG, "match recipient phone = " + phone); + if (D) Log.d(TAG, "match recipient phone = " + phone); res = true; } else { String name = getContactNameFromPhone(phone); if (name != null && name.length() > 0 && name.matches(recip)) { - Log.d(TAG, "match recipient name = " + name); + if (D) Log.d(TAG, "match recipient name = " + name); res = true; } else { res = false; @@ -762,7 +786,7 @@ public class BluetoothMapContent { } else if (fi.msgType == FilterInfo.TYPE_MMS) { res = matchRecipientMms(c, fi, recip); } else { - Log.d(TAG, "Unknown msg type: " + fi.msgType); + if (D) Log.d(TAG, "Unknown msg type: " + fi.msgType); res = false; } } else { @@ -777,12 +801,12 @@ public class BluetoothMapContent { String phone = getAddressMms(mResolver, id, MMS_FROM); if (phone != null && phone.length() > 0) { if (phone.matches(orig)) { - Log.d(TAG, "match originator phone = " + phone); + if (D) Log.d(TAG, "match originator phone = " + phone); res = true; } else { String name = getContactNameFromPhone(phone); if (name != null && name.length() > 0 && name.matches(orig)) { - Log.d(TAG, "match originator name = " + name); + if (D) Log.d(TAG, "match originator name = " + name); res = true; } else { res = false; @@ -801,12 +825,12 @@ public class BluetoothMapContent { String phone = c.getString(c.getColumnIndex(Sms.ADDRESS)); if (phone !=null && phone.length() > 0) { if (phone.matches(orig)) { - Log.d(TAG, "match originator phone = " + phone); + if (D) Log.d(TAG, "match originator phone = " + phone); res = true; } else { String name = getContactNameFromPhone(phone); if (name != null && name.length() > 0 && name.matches(orig)) { - Log.d(TAG, "match originator name = " + name); + if (D) Log.d(TAG, "match originator name = " + name); res = true; } else { res = false; @@ -820,10 +844,10 @@ public class BluetoothMapContent { String phone = fi.phoneNum; String name = fi.phoneAlphaTag; if (phone != null && phone.length() > 0 && phone.matches(orig)) { - Log.d(TAG, "match originator phone = " + phone); + if (D) Log.d(TAG, "match originator phone = " + phone); res = true; } else if (name != null && name.length() > 0 && name.matches(orig)) { - Log.d(TAG, "match originator name = " + name); + if (D) Log.d(TAG, "match originator name = " + name); res = true; } else { res = false; @@ -1073,7 +1097,7 @@ public class BluetoothMapContent { /* where += setWhereFilterOriginator(ap, fi); */ /* where += setWhereFilterRecipient(ap, fi); */ - Log.d(TAG, "where: " + where); + if (D) Log.d(TAG, "where: " + where); return where; } @@ -1114,7 +1138,7 @@ public class BluetoothMapContent { fi.phoneType = tm.getPhoneType(); fi.phoneNum = tm.getLine1Number(); fi.phoneAlphaTag = tm.getLine1AlphaTag(); - Log.d(TAG, "phone type = " + fi.phoneType + + if (D) Log.d(TAG, "phone type = " + fi.phoneType + " phone num = " + fi.phoneNum + " phone alpha tag = " + fi.phoneAlphaTag); } @@ -1178,7 +1202,7 @@ public class BluetoothMapContent { } public int msgListingSize(String folder, BluetoothMapAppParams ap) { - Log.d(TAG, "msgListingSize: folder = " + folder); + if (D) Log.d(TAG, "msgListingSize: folder = " + folder); int cnt = 0; /* Cache some info used throughout filtering */ @@ -1186,8 +1210,8 @@ public class BluetoothMapContent { setFilterInfo(fi); if (smsSelected(fi, ap)) { + fi.msgType = FilterInfo.TYPE_SMS; String where = setWhereFilter(folder, fi, ap); - Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null, "date DESC"); @@ -1196,9 +1220,67 @@ public class BluetoothMapContent { c.close(); } } - Log.d(TAG, "msgListingSize: size = " + cnt); + + if (mmsSelected(fi, ap)) { + fi.msgType = FilterInfo.TYPE_MMS; + String where = setWhereFilter(folder, fi, ap); + Cursor c = mResolver.query(Mms.CONTENT_URI, + MMS_PROJECTION, where, null, "date DESC"); + + if (c != null) { + cnt += c.getCount(); + c.close(); + } + } + + if (D) Log.d(TAG, "msgListingSize: size = " + cnt); return cnt; } + /** + * Return true if there are unread messages in the requested list of messages + * @param folder folder where the message listing should come from + * @param ap application parameter object + * @return true if unread messages are in the list, else false + */ + public boolean msgListingHasUnread(String folder, BluetoothMapAppParams ap) { + if (D) Log.d(TAG, "msgListingHasUnread: folder = " + folder); + int cnt = 0; + + /* Cache some info used throughout filtering */ + FilterInfo fi = new FilterInfo(); + setFilterInfo(fi); + + if (smsSelected(fi, ap)) { + fi.msgType = FilterInfo.TYPE_SMS; + String where = setWhereFilterFolderType(folder, fi); + where += " AND read=0 "; + where += setWhereFilterPeriod(ap, fi); + Cursor c = mResolver.query(Sms.CONTENT_URI, + SMS_PROJECTION, where, null, "date DESC"); + + if (c != null) { + cnt = c.getCount(); + c.close(); + } + } + + if (mmsSelected(fi, ap)) { + fi.msgType = FilterInfo.TYPE_MMS; + String where = setWhereFilterFolderType(folder, fi); + where += " AND read=0 "; + where += setWhereFilterPeriod(ap, fi); + Cursor c = mResolver.query(Mms.CONTENT_URI, + MMS_PROJECTION, where, null, "date DESC"); + + if (c != null) { + cnt += c.getCount(); + c.close(); + } + } + + if (D) Log.d(TAG, "msgListingHasUnread: numUnread = " + cnt); + return (cnt>0)?true:false; + } /** * Get the folder name of an SMS message or MMS message. @@ -1214,7 +1296,7 @@ public class BluetoothMapContent { case 1: return "inbox"; case 2: - return "send"; + return "sent"; case 3: return "draft"; case 4: // Just name outbox, failed and queued "outbox" @@ -1225,15 +1307,15 @@ public class BluetoothMapContent { return ""; } - public byte[] getMessage(String handle, int charset) throws UnsupportedEncodingException{ + public byte[] getMessage(String handle, BluetoothMapAppParams appParams) throws UnsupportedEncodingException{ TYPE type = BluetoothMapUtils.getMsgTypeFromHandle(handle); long id = BluetoothMapUtils.getCpHandle(handle); switch(type) { case SMS_GSM: case SMS_CDMA: - return getSmsMessage(id, charset); + return getSmsMessage(id, appParams.getCharset()); case MMS: - return getMmsMessage(id); + return getMmsMessage(id, appParams); case EMAIL: throw new IllegalArgumentException("Email not implemented - invalid message handle."); } @@ -1342,16 +1424,17 @@ public class BluetoothMapContent { String phone = c.getString(c.getColumnIndex(Sms.ADDRESS)); + time = c.getLong(c.getColumnIndex(Sms.DATE)); if(type == 1) // Inbox message needs to set the vCard as originator setVCardFromPhoneNumber(message, phone, true); else // Other messages sets the vCard as the recipient setVCardFromPhoneNumber(message, phone, false); if(charset == MAP_MESSAGE_CHARSET_NATIVE) { - if(type == 1) - message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone)); + if(type == 1) //Inbox + message.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus(msgBody, phone, time)); else - message.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus(msgBody, phone, time)); // TODO: No support for GSM + message.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus(msgBody, phone)); } else /*if (charset == MAP_MESSAGE_CHARSET_UTF8)*/ { message.setSmsBody(msgBody); } @@ -1380,19 +1463,19 @@ public class BluetoothMapContent { Integer type = c.getInt(c.getColumnIndex("type")); switch(type) { case MMS_FROM: - setVCardFromPhoneNumber(message, address, false); + setVCardFromPhoneNumber(message, address, true); message.addFrom(null, address); break; case MMS_TO: - setVCardFromPhoneNumber(message, address, true); + setVCardFromPhoneNumber(message, address, false); message.addTo(null, address); break; case MMS_CC: - setVCardFromPhoneNumber(message, address, true); + setVCardFromPhoneNumber(message, address, false); message.addCc(null, address); break; case MMS_BCC: - setVCardFromPhoneNumber(message, address, true); + setVCardFromPhoneNumber(message, address, false); message.addBcc(null, address); default: break; @@ -1401,6 +1484,11 @@ public class BluetoothMapContent { } } + /** + * Read out a mms data part and return the data in a byte array. + * @param partid the content provider id of the mms. + * @return + */ private byte[] readMmsDataPart(long partid) { String uriStr = String.format("content://mms/part/%d", partid); Uri uriAddress = Uri.parse(uriStr); @@ -1430,8 +1518,15 @@ public class BluetoothMapContent { return retVal; } + /** + * Read out the mms parts and update the bMessage object provided i {@linkplain message} + * @param id the content provider ID of the message + * @param message the bMessage object to add the information to + */ private void extractMmsParts(long id, BluetoothMapbMessageMmsEmail message) { + /* TODO: If the attachment appParam is set to "no", only add the text parts. + * (content type contains "text" - case insensitive) */ final String[] projection = null; String selection = new String("mid=" + id); String uriStr = String.format("content://mms/%d/part", id); @@ -1452,17 +1547,28 @@ public class BluetoothMapContent { String filename = c.getString(c.getColumnIndex("fn")); String text = c.getString(c.getColumnIndex("text")); Integer fd = c.getInt(c.getColumnIndex("_data")); + String cid = c.getString(c.getColumnIndex("cid")); + String cl = c.getString(c.getColumnIndex("cl")); + String cdisp = c.getString(c.getColumnIndex("cd")); + + if(D) Log.d(TAG, " _id : " + partId + + "\n ct : " + contentType + + "\n partname : " + name + + "\n charset : " + charset + + "\n filename : " + filename + + "\n text : " + text + + "\n fd : " + fd + + "\n cid : " + cid + + "\n cl : " + cl + + "\n cdisp : " + cdisp); - if(D)Log.d(TAG, " _id : " + partId + - "\n ct : " + contentType + - "\n partname : " + name + - "\n charset : " + charset + - "\n filename : " + filename + - "\n text : " + text + - "\n fd : " + fd); part = message.addMimePart(); part.contentType = contentType; part.partName = name; + part.contentId = cid; + part.contentLocation = cl; + part.contentDisposition = cdisp; + try { if(text != null) { part.data = text.getBytes("UTF-8"); @@ -1489,7 +1595,15 @@ public class BluetoothMapContent { message.updateCharset(); } - public byte[] getMmsMessage(long id) throws UnsupportedEncodingException { + /** + * + * @param id the content provider id for the message to fetch. + * @param appParams The application parameter object received from the client. + * @return a byte[] containing the utf-8 encoded bMessage to send to the client. + * @throws UnsupportedEncodingException if UTF-8 is not supported, + * which is guaranteed to be supported on an android device + */ + public byte[] getMmsMessage(long id, BluetoothMapAppParams appParams) throws UnsupportedEncodingException { int msgBox, threadId; BluetoothMapbMessageMmsEmail message = new BluetoothMapbMessageMmsEmail(); Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, "_ID = " + id, null, null); @@ -1512,7 +1626,8 @@ public class BluetoothMapContent { message.setMessageId(c.getString(c.getColumnIndex(Mms.MESSAGE_ID))); message.setContentType(c.getString(c.getColumnIndex(Mms.CONTENT_TYPE))); message.setDate(c.getLong(c.getColumnIndex(Mms.DATE)) * 1000L); - // c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)); - TODO: Do we need this + message.setTextOnly(c.getInt(c.getColumnIndex(Mms.TEXT_ONLY)) == 0 ? false : true); // - TODO: Do we need this - yes, if we have only text, we should not make this a multipart message + message.setIncludeAttachemnts(appParams.getAttachment() == 0 ? false : true); // c.getLong(c.getColumnIndex(Mms.DATE_SENT)); - this is never used // c.getInt(c.getColumnIndex(Mms.STATUS)); - don't know what this is diff --git a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java index ed090ea1d..deadf841b 100644 --- a/src/com/android/bluetooth/map/BluetoothMapContentObserver.java +++ b/src/com/android/bluetooth/map/BluetoothMapContentObserver.java @@ -64,8 +64,8 @@ import com.google.android.mms.pdu.PduHeaders; public class BluetoothMapContentObserver { private static final String TAG = "BluetoothMapContentObserver"; - private static final boolean D = true; - private static final boolean V = true; + private static final boolean D = false; + private static final boolean V = false; private Context mContext; private ContentResolver mResolver; @@ -195,7 +195,7 @@ public class BluetoothMapContentObserver { xmlEvtReport.setOutput(sw); xmlEvtReport.startDocument(null, null); xmlEvtReport.text("\n"); - xmlEvtReport.startTag("", "Map-event-report"); + xmlEvtReport.startTag("", "MAP-event-report"); xmlEvtReport.attribute("", "version", "1.0"); xmlEvtReport.startTag("", "event"); @@ -210,7 +210,7 @@ public class BluetoothMapContentObserver { xmlEvtReport.attribute("", "msg_type", msgType.name()); xmlEvtReport.endTag("", "event"); - xmlEvtReport.endTag("", "Map-event-report"); + xmlEvtReport.endTag("", "MAP-event-report"); xmlEvtReport.endDocument(); } catch (IllegalArgumentException e) { e.printStackTrace(); @@ -254,6 +254,7 @@ public class BluetoothMapContentObserver { public void unregisterObserver() { if (V) Log.d(TAG, "unregisterObserver"); mResolver.unregisterContentObserver(mObserver); + mMnsClient = null; } private void sendEvent(Event evt) { @@ -771,6 +772,7 @@ public class BluetoothMapContentObserver { values.put("read", 0); values.put("seen", 0); + values.put("sub", msg.getSubject()); values.put("sub_cs", 106); values.put("ct_t", "application/vnd.wap.multipart.related"); values.put("exp", 604800); @@ -782,6 +784,11 @@ public class BluetoothMapContentObserver { values.put("tr_id", "T"+ Long.toHexString(System.currentTimeMillis())); values.put("d_rpt", PduHeaders.VALUE_NO); values.put("locked", 0); + if(msg.getTextOnly() == true) + values.put("text_only", true); + + values.put("m_size", msg.getSize()); + // Get thread id Set<String> recipients = new HashSet<String>(); recipients.addAll(Arrays.asList(to_address)); @@ -802,51 +809,64 @@ public class BluetoothMapContentObserver { Log.v(TAG, " NEW URI " + uri.toString()); } try { + if(V) Log.v(TAG, "Adding " + msg.getMimeParts().size() + " parts to the data base."); for(MimePart part : msg.getMimeParts()) { + int count = 0; + count++; values.clear(); if(part.contentType != null && part.contentType.toUpperCase().contains("TEXT")) { - values.put("seq", 0); values.put("ct", "text/plain"); - values.put("name", "null"); values.put("chset", 106); - values.put("cd", "null"); - values.put("fn", part.partName); - values.put("name", part.partName); - values.put("cid", "<smil>"); - values.put("cl", part.partName); - values.put("ctt_s", "null"); - values.put("ctt_t", "null"); - values.put("_data", "null"); + if(part.partName != null) { + values.put("fn", part.partName); + values.put("name", part.partName); + } else if(part.contentId == null && part.contentLocation == null) { + /* We must set at least one part identifier */ + values.put("fn", "text_" + count +".txt"); + values.put("name", "text_" + count +".txt"); + } + if(part.contentId != null) { + values.put("cid", part.contentId); + } + if(part.contentLocation != null) + values.put("cl", part.contentLocation); + if(part.contentDisposition != null) + values.put("cd", part.contentDisposition); values.put("text", new String(part.data, "UTF-8")); uri = Uri.parse("content://mms/" + handle + "/part"); uri = cr.insert(uri, values); + if(V) Log.v(TAG, "Added TEXT part"); } else if (part.contentType != null && part.contentType.toUpperCase().contains("SMIL")){ values.put("seq", -1); values.put("ct", "application/smil"); - values.put("cid", "<smil>"); - values.put("cl", "smil.xml"); + if(part.contentId != null) + values.put("cid", part.contentId); + if(part.contentLocation != null) + values.put("cl", part.contentLocation); + if(part.contentDisposition != null) + values.put("cd", part.contentDisposition); values.put("fn", "smil.xml"); values.put("name", "smil.xml"); values.put("text", new String(part.data, "UTF-8")); uri = Uri.parse("content://mms/" + handle + "/part"); uri = cr.insert(uri, values); + if(V) Log.v(TAG, "Added SMIL part"); }else /*VIDEO/AUDIO/IMAGE*/ { - writeMmsDataPart(handle, part.contentType, part.partName, part.data); + writeMmsDataPart(handle, part, count); + if(V) Log.v(TAG, "Added OTHER part"); } if (uri != null && V){ Log.v(TAG, "Added part with content-type: "+ part.contentType + " to Uri: " + uri.toString()); } } } catch (UnsupportedEncodingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + Log.w(TAG, e); } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + Log.w(TAG, e); } values.clear(); @@ -876,27 +896,32 @@ public class BluetoothMapContentObserver { } - private void writeMmsDataPart(long handle, String contentType, String name, byte[] data) throws IOException{ + private void writeMmsDataPart(long handle, MimePart part, int count) throws IOException{ ContentValues values = new ContentValues(); values.put("mid", handle); - values.put("ct", contentType); - values.put("cid", "<smil>"); - values.put("cl", name); - values.put("fn", name); - values.put("name", name); + if(part.contentType != null) + values.put("ct", part.contentType); + if(part.contentId != null) + values.put("cid", part.contentId); + if(part.contentLocation != null) + values.put("cl", part.contentLocation); + if(part.contentDisposition != null) + values.put("cd", part.contentDisposition); + if(part.partName != null) { + values.put("fn", part.partName); + values.put("name", part.partName); + } else if(part.contentId == null && part.contentLocation == null) { + /* We must set at least one part identifier */ + values.put("fn", "part_" + count + ".dat"); + values.put("name", "part_" + count + ".dat"); + } Uri partUri = Uri.parse("content://mms/" + handle + "/part"); Uri res = mResolver.insert(partUri, values); // Add data to part OutputStream os = mResolver.openOutputStream(res); - ByteArrayInputStream is = new ByteArrayInputStream(data); - byte[] buffer = new byte[256]; - for (int len=0; (len=is.read(buffer)) != -1;) - { - os.write(buffer, 0, len); - } + os.write(part.data); os.close(); - is.close(); } diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java index 0e5ba97d7..ffa05683e 100644 --- a/src/com/android/bluetooth/map/BluetoothMapMessageListing.java +++ b/src/com/android/bluetooth/map/BluetoothMapMessageListing.java @@ -27,7 +27,7 @@ import android.util.Log; import android.util.Xml; public class BluetoothMapMessageListing { - + private boolean hasUnread = false; private static final String TAG = "BluetoothMapMessageListing"; private List<BluetoothMapMessageListingElement> list; @@ -36,6 +36,11 @@ public class BluetoothMapMessageListing { } public void add(BluetoothMapMessageListingElement element) { list.add(element); + /* update info regarding whether the list contains unread messages */ + if (element.getRead().equalsIgnoreCase("no")) + { + hasUnread = true; + } } /** @@ -43,8 +48,22 @@ public class BluetoothMapMessageListing { * @return the number of elements in the list. */ public int getCount() { - return list.size(); + if(list != null) + { + return list.size(); + } + return 0; + } + + /** + * does the list contain any unread messages + * @return true if unread messages have been added to the list, else false + */ + public boolean hasUnread() + { + return hasUnread; } + /** * Encode the list of BluetoothMapMessageListingElement(s) into a UTF-8 * formatted XML-string in a trimmed byte array diff --git a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java index 1870486bf..9f70759c1 100644 --- a/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java +++ b/src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java @@ -32,10 +32,11 @@ public class BluetoothMapMessageListingElement implements Comparable<BluetoothMapMessageListingElement> { private static final String TAG = "BluetoothMapMessageListingElement"; - private static final boolean D = true; - private static final boolean V = true; + private static final boolean D = false; + private static final boolean V = false; - private long handle = 0; + private long cpHandle = 0; /* The content provider handle - without type information */ + private String mapHandle = null; /* The map hex-string handle with type information */ private String subject = null; private long dateTime = 0; private String senderName = null; @@ -52,13 +53,14 @@ public class BluetoothMapMessageListingElement private String read = null; private String sent = null; private String protect = null; - + private boolean reportRead; public long getHandle() { - return handle; + return cpHandle; } - public void setHandle(long handle) { - this.handle = handle; + public void setHandle(long handle, TYPE type) { + this.cpHandle = handle; + this.mapHandle = BluetoothMapUtils.getMapHandle(cpHandle, type); } public long getDateTime() { @@ -181,8 +183,9 @@ public class BluetoothMapMessageListingElement return read; } - public void setRead(String read) { + public void setRead(String read, boolean reportRead) { this.read = read; + this.reportRead = reportRead; } public String getSent() { @@ -213,43 +216,46 @@ public class BluetoothMapMessageListingElement /* Encode the MapMessageListingElement into the StringBuilder reference. * */ - public void encode(XmlSerializer xmlMsgElement) throws IllegalArgumentException, - IllegalStateException, IOException + public void encode(XmlSerializer xmlMsgElement) throws IllegalArgumentException, IllegalStateException, IOException { - // contruct the XML tag for a single msg in the msglisting - xmlMsgElement.startTag("", "msg"); - xmlMsgElement.attribute("", "handle", BluetoothMapUtils.getMapHandle(handle, type)); - xmlMsgElement.attribute("", "subject", subject); - xmlMsgElement.attribute("", "datetime", this.getDateTimeString()); - if (senderName != null) - xmlMsgElement.attribute("", "sender_name", senderName); - if (senderAddressing != null) - xmlMsgElement.attribute("", "sender_addressing", senderAddressing); - if (replytoAddressing != null) - xmlMsgElement.attribute("", "replyto_addressing",replytoAddressing); - if (recipientName != null) - xmlMsgElement.attribute("", "recipient_name",recipientName); - if (recipientAddressing != null) - xmlMsgElement.attribute("", "recipient_addressing", recipientAddressing); - if (type != null) - xmlMsgElement.attribute("", "type", type.name()); - if (size != -1) - xmlMsgElement.attribute("", "size", Integer.toString(size)); - if (text != null) - xmlMsgElement.attribute("", "text", text); - if (receptionStatus != null) - xmlMsgElement.attribute("", "reception_status", receptionStatus); - if (attachmentSize != -1) - xmlMsgElement.attribute("", "attachment_size", Integer.toString(attachmentSize)); - if (priority != null) - xmlMsgElement.attribute("", "priority", priority); - if (read != null) - xmlMsgElement.attribute("", "read", read); - if (sent != null) - xmlMsgElement.attribute("", "sent", sent); - if (protect != null) - xmlMsgElement.attribute("", "protect", protect); - xmlMsgElement.endTag("", "msg"); + + // contruct the XML tag for a single msg in the msglisting + xmlMsgElement.startTag("", "msg"); + xmlMsgElement.attribute("", "handle", mapHandle); + if(subject != null) + xmlMsgElement.attribute("", "subject", subject); + if(dateTime != 0) + xmlMsgElement.attribute("", "datetime", this.getDateTimeString()); + if(senderName != null) + xmlMsgElement.attribute("", "sender_name", senderName); + if(senderAddressing != null) + xmlMsgElement.attribute("", "sender_addressing", senderAddressing); + if(replytoAddressing != null) + xmlMsgElement.attribute("", "replyto_addressing",replytoAddressing); + if(recipientName != null) + xmlMsgElement.attribute("", "recipient_name",recipientName); + if(recipientAddressing != null) + xmlMsgElement.attribute("", "recipient_addressing", recipientAddressing); + if(type != null) + xmlMsgElement.attribute("", "type", type.name()); + if(size != -1) + xmlMsgElement.attribute("", "size", Integer.toString(size)); + if(text != null) + xmlMsgElement.attribute("", "text", text); + if(receptionStatus != null) + xmlMsgElement.attribute("", "reception_status", receptionStatus); + if(attachmentSize != -1) + xmlMsgElement.attribute("", "attachment_size", Integer.toString(attachmentSize)); + if(priority != null) + xmlMsgElement.attribute("", "priority", priority); + if(read != null && reportRead) + xmlMsgElement.attribute("", "read", read); + if(sent != null) + xmlMsgElement.attribute("", "sent", sent); + if(protect != null) + xmlMsgElement.attribute("", "protect", protect); + xmlMsgElement.endTag("", "msg"); + } } diff --git a/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/src/com/android/bluetooth/map/BluetoothMapObexServer.java index c1a82c2d4..e6d60fce3 100644 --- a/src/com/android/bluetooth/map/BluetoothMapObexServer.java +++ b/src/com/android/bluetooth/map/BluetoothMapObexServer.java @@ -44,11 +44,11 @@ public class BluetoothMapObexServer extends ServerRequestHandler { // 128 bit UUID for MAP private static final byte[] MAP_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 - }; + (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 + }; /* Message types */ private static final String TYPE_GET_FOLDER_LISTING = "x-obex/folder-listing"; @@ -56,7 +56,6 @@ public class BluetoothMapObexServer extends ServerRequestHandler { private static final String TYPE_MESSAGE = "x-bt/message"; private static final String TYPE_SET_MESSAGE_STATUS = "x-bt/messageStatus"; private static final String TYPE_SET_NOTIFICATION_REGISTRATION = "x-bt/MAP-NotificationRegistration"; - private static final String TYPE_SEND_EVENT = "x-bt/MAP-event-report"; private static final String TYPE_MESSAGE_UPDATE = "x-bt/MAP-messageUpdate"; private BluetoothMapFolderElement mCurrentFolder; @@ -135,6 +134,7 @@ public class BluetoothMapObexServer extends ServerRequestHandler { if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " + "MSG_SESSION_ESTABLISHED msg."); + Message msg = Message.obtain(mCallback); msg.what = BluetoothMapService.MSG_SESSION_ESTABLISHED; msg.sendToTarget(); @@ -239,10 +239,9 @@ public class BluetoothMapObexServer extends ServerRequestHandler { if(D) Log.d(TAG, "Push message only allowed to outbox and draft. folderName: " + folderName); return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; } - /* TODO: - * - Read out the message - OK - * - Decode into a bMessage - OK - * - push to draft or send. + /* - Read out the message + * - Decode into a bMessage + * - send it. */ InputStream bMsgStream; BluetoothMapbMessage message; @@ -264,6 +263,8 @@ public class BluetoothMapObexServer extends ServerRequestHandler { if (D) Log.d(TAG, "handleStr: " + handleStr + " message.getType(): " + message.getType()); replyHeaders.setHeader(HeaderSet.NAME, handleStr); op.sendHeaders(replyHeaders); + + bMsgStream.close(); } catch (IllegalArgumentException e) { if(D) Log.w(TAG, "Wrongly formatted bMessage received", e); return ResponseCodes.OBEX_HTTP_PRECON_FAILED; @@ -278,6 +279,9 @@ public class BluetoothMapObexServer extends ServerRequestHandler { private int setMessageStatus(String msgHandle, BluetoothMapAppParams appParams) { int indicator = appParams.getStatusIndicator(); int value = appParams.getStatusValue(); + long handle; + BluetoothMapUtils.TYPE msgType; + if(indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER || value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER || msgHandle == null) { @@ -288,8 +292,14 @@ public class BluetoothMapObexServer extends ServerRequestHandler { return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen. } - long handle = BluetoothMapUtils.getCpHandle(msgHandle); - BluetoothMapUtils.TYPE msgType = BluetoothMapUtils.getMsgTypeFromHandle(msgHandle); + try { + handle = BluetoothMapUtils.getCpHandle(msgHandle); + msgType = BluetoothMapUtils.getMsgTypeFromHandle(msgHandle); + } catch (NumberFormatException e) { + Log.w(TAG, "Wrongly formatted message handle: " + msgHandle); + return ResponseCodes.OBEX_HTTP_PRECON_FAILED; + } + if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_DELETED) { if (!observer.setMessageStatusDeleted(handle, msgType, value)) { return ResponseCodes.OBEX_HTTP_UNAVAILABLE; @@ -398,10 +408,9 @@ public class BluetoothMapObexServer extends ServerRequestHandler { return sendMessageListingRsp(op, appParams, name); // Block until all packets have been send. } else if (type.equals(TYPE_MESSAGE)){ - if (V && appParams != null) { - Log.d(TAG,"TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() + - ", Charset = " + appParams.getCharset() + - ", FractionRequest = " + appParams.getFractionRequest()); + if(V && appParams != null) { + Log.d(TAG,"TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() + ", Charset = " + appParams.getCharset() + + ", FractionRequest = " + appParams.getFractionRequest()); } return sendGetMessageRsp(op, name, appParams); // Block until all packets have been send. } @@ -433,19 +442,20 @@ public class BluetoothMapObexServer extends ServerRequestHandler { OutputStream outStream = null; byte[] outBytes = null; int maxChunkSize, bytesToWrite, bytesWritten = 0, listSize; + boolean hasUnread = false; HeaderSet replyHeaders = new HeaderSet(); BluetoothMapAppParams outAppParams = new BluetoothMapAppParams(); BluetoothMapMessageListing outList; - if (folderName == null) { + if(folderName == null) { folderName = mCurrentFolder.getName(); } - if (appParams == null){ + if(appParams == null){ appParams = new BluetoothMapAppParams(); appParams.setMaxListCount(1024); appParams.setStartOffset(0); } - // TODO: Check to see if we only need to send the size - hence no need to encode. + // Check to see if we only need to send the size - hence no need to encode. try { // Open the OBEX body stream outStream = op.openOutputStream(); @@ -460,27 +470,28 @@ public class BluetoothMapObexServer extends ServerRequestHandler { outList = mOutContent.msgListing(folderName, appParams); // Generate the byte stream outAppParams.setMessageListingSize(outList.getCount()); -// if(outList.getCount() != 0) { - outBytes = outList.encode(); -// } else { -// op.noBodyHeader(); - // TODO: Remove after test - //Log.w(TAG,"sendMessageListingRsp: Empty list - sending OBEX_HTTP_BAD_REQUEST"); - //return ResponseCodes.OBEX_HTTP_BAD_REQUEST; -// } + outBytes = outList.encode(); + hasUnread = outList.hasUnread(); } else { listSize = mOutContent.msgListingSize(folderName, appParams); + hasUnread = mOutContent.msgListingHasUnread(folderName, appParams); outAppParams.setMessageListingSize(listSize); op.noBodyHeader(); } // Build the application parameter header - outAppParams.setNewMessage(0); // TODO: set depending on new messages - outAppParams.setMseTime(Calendar.getInstance().getTime().getTime()); - replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams()); + // let the peer know if there are unread messages in the list + if(hasUnread) + { + outAppParams.setNewMessage(1); + }else{ + outAppParams.setNewMessage(0); + } + outAppParams.setMseTime(Calendar.getInstance().getTime().getTime()); + replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams()); op.sendHeaders(replyHeaders); } catch (IOException e) { @@ -489,13 +500,10 @@ public class BluetoothMapObexServer extends ServerRequestHandler { } catch (IllegalArgumentException e) { Log.w(TAG,"sendMessageListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST", e); return ResponseCodes.OBEX_HTTP_BAD_REQUEST; - } catch (Exception e){ - Log.w(TAG, "Exception:", e); - // TODO: REMOVE AFTER TEST!! } maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers. - if (outBytes != null) { + if(outBytes != null) { try { while (bytesWritten < outBytes.length && sIsAborted == false) { bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten); @@ -505,11 +513,8 @@ public class BluetoothMapObexServer extends ServerRequestHandler { } catch (IOException e) { if(V) Log.w(TAG,e); // We were probably aborted or disconnected - } catch (Exception e){ - if(V) Log.w(TAG,e); - // TODO: REMOVE AFTER TEST!! } finally { - if (outStream != null) { + if(outStream != null) { try { outStream.close(); } catch (IOException e) { @@ -517,7 +522,7 @@ public class BluetoothMapObexServer extends ServerRequestHandler { } } } - if (bytesWritten != outBytes.length) + if(bytesWritten != outBytes.length) return ResponseCodes.OBEX_HTTP_BAD_REQUEST; } else { try { @@ -618,26 +623,26 @@ public class BluetoothMapObexServer extends ServerRequestHandler { } /** - * Generate and send the Folder listing response based on an application - * parameter header. This function call will block until complete or aborted - * by the peer. Fragmentation of packets larger than the obex packet size - * will be handled by this function. + * Generate and send the get message response based on an application + * parameter header and a handle. * * @param op * The OBEX operation. * @param appParams * The application parameter header + * @param handle + * The handle of the requested message * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or * {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error. */ - private int sendGetMessageRsp(Operation op, String name, BluetoothMapAppParams appParams){ + private int sendGetMessageRsp(Operation op, String handle, BluetoothMapAppParams appParams){ OutputStream outStream ; byte[] outBytes; int maxChunkSize, bytesToWrite, bytesWritten = 0; long msgHandle; try { - outBytes = mOutContent.getMessage(name, appParams.getCharset()); + outBytes = mOutContent.getMessage(handle, appParams); outStream = op.openOutputStream(); } catch (IOException e) { @@ -678,7 +683,7 @@ public class BluetoothMapObexServer extends ServerRequestHandler { } - public static final void logHeader(HeaderSet hs) { + private static final void logHeader(HeaderSet hs) { Log.v(TAG, "Dumping HeaderSet " + hs.toString()); try { Log.v(TAG, "CONNECTION_ID : " + hs.getHeader(HeaderSet.CONNECTION_ID)); diff --git a/src/com/android/bluetooth/map/BluetoothMapReceiver.java b/src/com/android/bluetooth/map/BluetoothMapReceiver.java deleted file mode 100644 index 7363e0048..000000000 --- a/src/com/android/bluetooth/map/BluetoothMapReceiver.java +++ /dev/null @@ -1,64 +0,0 @@ -/* -* Copyright (C) 2013 Samsung System LSI -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -package com.android.bluetooth.map; - -import android.bluetooth.BluetoothAdapter; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.util.Log; - -public class BluetoothMapReceiver extends BroadcastReceiver { - - private static final String TAG = "BluetoothMapReceiver"; - - private static final boolean V = BluetoothMapService.VERBOSE; - - @Override - public void onReceive(Context context, Intent intent) { - if (V) Log.v(TAG, "MapReceiver onReceive "); - - Intent in = new Intent(); - in.putExtras(intent); - in.setClass(context, BluetoothMapService.class); - String action = intent.getAction(); - in.putExtra("action", action); - if (V) Log.v(TAG,"***********action = " + action); - - boolean startService = true; - if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { - int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); - in.putExtra(BluetoothAdapter.EXTRA_STATE, state); - if (V) Log.v(TAG,"***********state = " + state); - if ((state == BluetoothAdapter.STATE_TURNING_ON) - || (state == BluetoothAdapter.STATE_OFF)) { - //FIX: We turn on MAP after BluetoothAdapter.STATE_ON, - //but we turn off MAP right after BluetoothAdapter.STATE_TURNING_OFF - startService = false; - } - } else { - // Don't forward intent unless device has bluetooth and bluetooth is enabled. - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter == null || !adapter.isEnabled()) { - startService = false; - } - } - if (startService) { - if (V) Log.v(TAG,"***********Calling start service!!!! with action = " + in.getAction()); - context.startService(in); - } - } -} diff --git a/src/com/android/bluetooth/map/BluetoothMapService.java b/src/com/android/bluetooth/map/BluetoothMapService.java index e4da10f11..6e97c5592 100644 --- a/src/com/android/bluetooth/map/BluetoothMapService.java +++ b/src/com/android/bluetooth/map/BluetoothMapService.java @@ -16,6 +16,9 @@ package com.android.bluetooth.map; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; import javax.obex.ServerSession; import android.app.Notification; @@ -37,15 +40,21 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; +import android.os.ParcelUuid; import android.text.TextUtils; import android.util.Log; +import android.provider.Settings; +import android.content.IntentFilter; +import android.content.BroadcastReceiver; import com.android.bluetooth.R; import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder; -public class BluetoothMapService extends Service { +public class BluetoothMapService extends ProfileService { private static final String TAG = "BluetoothMapService"; /** @@ -57,7 +66,7 @@ public class BluetoothMapService extends Service { public static final boolean DEBUG = true; - public static final boolean VERBOSE = true; + public static final boolean VERBOSE = false; /** * Intent indicating incoming obex authentication request which is from @@ -66,18 +75,6 @@ public class BluetoothMapService extends Service { public static final String AUTH_CHALL_ACTION = "com.android.bluetooth.map.authchall"; /** - * Intent indicating obex session key input complete by user which is sent - * from BluetoothMapActivity - */ - public static final String AUTH_RESPONSE_ACTION = "com.android.bluetooth.map.authresponse"; - - /** - * Intent indicating user canceled obex authentication session key input - * which is sent from BluetoothMapActivity - */ - public static final String AUTH_CANCELLED_ACTION = "com.android.bluetooth.map.authcancelled"; - - /** * Intent indicating timeout for user confirmation, which is sent to * BluetoothMapActivity */ @@ -108,16 +105,7 @@ public class BluetoothMapService extends Service { private static final int USER_TIMEOUT = 2; - private static final int AUTH_TIMEOUT = 3; - - - private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000; - - - // Ensure not conflict with Opp notification ID - private static final int NOTIFICATION_ID_ACCESS = -1000001; - - private static final int NOTIFICATION_ID_AUTH = -1000002; + private static final int DISCONNECT_MAP = 3; private PowerManager.WakeLock mWakeLock = null; @@ -139,166 +127,30 @@ public class BluetoothMapService extends Service { private BluetoothDevice mRemoteDevice = null; - private static String sLocalPhoneNum = null; - - private static String sLocalPhoneName = null; - private static String sRemoteDeviceName = null; - private boolean mHasStarted = false; - private volatile boolean mInterrupted; private int mState; - private int mStartId = -1; - - //private IBluetooth mBluetoothService; - private boolean isWaitingAuthorization = false; - // package and class name to which we send intent to check phone book access permission + // package and class name to which we send intent to check message access access permission private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings"; private static final String ACCESS_AUTHORITY_CLASS = "com.android.settings.bluetooth.BluetoothPermissionRequest"; + private static final ParcelUuid[] MAP_UUIDS = { + BluetoothUuid.MAP, + BluetoothUuid.MNS, + }; + public BluetoothMapService() { mState = BluetoothMap.STATE_DISCONNECTED; } - @Override - public void onCreate() { - super.onCreate(); - if (VERBOSE) Log.v(TAG, "Map Service onCreate"); - - mInterrupted = false; - mAdapter = BluetoothAdapter.getDefaultAdapter(); - - if (!mHasStarted) { - mHasStarted = true; - if (VERBOSE) Log.v(TAG, "Starting MAP service"); - - int state = mAdapter.getState(); - if (state == BluetoothAdapter.STATE_ON) { - // start RFCOMM listener - mSessionStatusHandler.sendMessage(mSessionStatusHandler - .obtainMessage(START_LISTENER)); - } - } - } - // incoming Start intent handler - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - mStartId = startId; - if (mAdapter == null) { - Log.w(TAG, "Stopping BluetoothMapService: " - + "device does not have BT or device is not ready"); - // Release all resources - closeService(); - } else { - // No need to handle the null intent case, because we have - // all restart work done in onCreate() - if (intent != null) { - parseIntent(intent); - } - } - return START_NOT_STICKY; - } - - // process the intent from receiver - private void parseIntent(final Intent intent) { - String action = intent.getStringExtra("action"); - if (VERBOSE) Log.v(TAG, "action: " + action); - - int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); - if (VERBOSE) Log.v(TAG, "state: " + state); - - boolean removeTimeoutMsg = true; - // BT status have been changed check new state - if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { - if (state == BluetoothAdapter.STATE_TURNING_OFF) { - // Send any pending timeout now, as this service will be destroyed. - if (mSessionStatusHandler.hasMessages(USER_TIMEOUT)) { - Intent timeoutIntent = - new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); - timeoutIntent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); - sendBroadcast(timeoutIntent, BLUETOOTH_ADMIN_PERM); - } - // Release all resources - closeService(); - } else { - removeTimeoutMsg = false; - } - // Authorization answer intent - } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { - if (!isWaitingAuthorization) { - // this reply is not for us - return; - } - - isWaitingAuthorization = false; - - if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, - BluetoothDevice.CONNECTION_ACCESS_NO) == - BluetoothDevice.CONNECTION_ACCESS_YES) { - //bluetooth connection accepted by user - if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { - boolean result = mRemoteDevice.setTrust(true); - if (VERBOSE) Log.v(TAG, "setTrust() result=" + result); - } - try { - if (mConnSocket != null) { - // start obex server and rfcomm connection - startObexServerSession(); - } else { - stopObexServerSession(); - } - } catch (IOException ex) { - Log.e(TAG, "Caught the error: " + ex.toString()); - } - } else { - stopObexServerSession(); - } - } else if (action.equals(AUTH_RESPONSE_ACTION)) { - String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY); - //send auth request - notifyAuthKeyInput(sessionkey); - } else if (action.equals(AUTH_CANCELLED_ACTION)) { - //user cancelled auth request - notifyAuthCancelled(); - } else { - removeTimeoutMsg = false; - } - - if (removeTimeoutMsg) { - mSessionStatusHandler.removeMessages(USER_TIMEOUT); - } - } - - @Override - public void onDestroy() { - if (VERBOSE) Log.v(TAG, "Map Service onDestroy"); - - super.onDestroy(); - setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); - if (mWakeLock != null) { - mWakeLock.release(); - mWakeLock = null; - } - closeService(); - if(mSessionStatusHandler != null) { - mSessionStatusHandler.removeCallbacksAndMessages(null); - } - } - - @Override - public IBinder onBind(Intent intent) { - if (VERBOSE) Log.v(TAG, "Map Service onBind"); - return mBinder; - } - private void startRfcommSocketListener() { - if (VERBOSE) Log.v(TAG, "Map Service startRfcommSocketListener"); + if (DEBUG) Log.d(TAG, "Map Service startRfcommSocketListener"); if (mAcceptThread == null) { mAcceptThread = new SocketAcceptThread(); @@ -308,18 +160,19 @@ public class BluetoothMapService extends Service { } private final boolean initSocket() { - if (VERBOSE) Log.v(TAG, "Map Service initSocket"); + if (DEBUG) Log.d(TAG, "Map Service initSocket"); - boolean initSocketOK = true; + boolean initSocketOK = false; final int CREATE_RETRY_TIME = 10; // It's possible that create will fail in some cases. retry for 10 times - for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) { + for (int i = 0; (i < CREATE_RETRY_TIME) && !mInterrupted; i++) { + initSocketOK = true; try { - // It is mandatory for PSE to support initiation of bonding and + // It is mandatory for MSE to support initiation of bonding and // encryption. mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord - ("OBEX Message Access Server", BluetoothUuid.MAP.getUuid()); + ("MAP SMS/MMS", BluetoothUuid.MAS.getUuid()); } catch (IOException e) { Log.e(TAG, "Error create RfcommServerSocket " + e.toString()); @@ -334,19 +187,21 @@ public class BluetoothMapService extends Service { Log.w(TAG, "initServerSocket failed as BT is (being) turned off"); break; } - synchronized (this) { - try { - if (VERBOSE) Log.v(TAG, "wait 300 ms"); - Thread.sleep(300); - } catch (InterruptedException e) { - Log.e(TAG, "socketAcceptThread thread was interrupted (3)"); - mInterrupted = true; - } + try { + if (VERBOSE) Log.v(TAG, "wait 300 ms"); + Thread.sleep(300); + } catch (InterruptedException e) { + Log.e(TAG, "socketAcceptThread thread was interrupted (3)"); } } else { break; } } + if (mInterrupted) { + initSocketOK = false; + // close server socket to avoid resource leakage + closeServerSocket(); + } if (initSocketOK) { if (VERBOSE) Log.v(TAG, "Succeed to create listening socket "); @@ -357,68 +212,67 @@ public class BluetoothMapService extends Service { return initSocketOK; } - private final void closeSocket(boolean server, boolean accept) throws IOException { - if (server == true) { - // Stop the possible trying to init serverSocket - mInterrupted = true; - - if (mServerSocket != null) { + private final synchronized void closeServerSocket() { + // exit SocketAcceptThread early + if (mServerSocket != null) { + try { + // this will cause mServerSocket.accept() return early with IOException mServerSocket.close(); mServerSocket = null; + } catch (IOException ex) { + Log.e(TAG, "Close Server Socket error: " + ex); } } - - if (accept == true) { - if (mConnSocket != null) { + } + private final synchronized void closeConnectionSocket() { + if (mConnSocket != null) { + try { mConnSocket.close(); mConnSocket = null; + } catch (IOException e) { + Log.e(TAG, "Close Connection Socket error: " + e.toString()); } } } private final void closeService() { - if (VERBOSE) Log.v(TAG, "Map Service closeService in"); - - try { - closeSocket(true, true); - } catch (IOException ex) { - Log.e(TAG, "CloseSocket error: " + ex); - } - - if (mAcceptThread != null) { - try { - mAcceptThread.shutdown(); - mAcceptThread.join(); - mAcceptThread = null; - } catch (InterruptedException ex) { - Log.w(TAG, "mAcceptThread close error", ex); - } - } - if (mServerSession != null) { - mServerSession.close(); - mServerSession = null; - } - if (mBluetoothMnsObexClient != null) { - try { - mBluetoothMnsObexClient.interrupt(); - mBluetoothMnsObexClient.join(); - mBluetoothMnsObexClient = null; - } catch (InterruptedException ex) { - Log.w(TAG, "mBluetoothMnsObexClient close error", ex); - } - } -// mBluetoothMnsObexClient.shutdown - - mHasStarted = false; - if (mStartId != -1 && stopSelfResult(mStartId)) { - if (VERBOSE) Log.v(TAG, "successfully stopped map service"); - mStartId = -1; - } - if (VERBOSE) Log.v(TAG, "Map Service closeService out"); + if (DEBUG) Log.d(TAG, "MAP Service closeService in"); + + // exit initSocket early + mInterrupted = true; + closeServerSocket(); + + if (mAcceptThread != null) { + try { + mAcceptThread.shutdown(); + mAcceptThread.join(); + mAcceptThread = null; + } catch (InterruptedException ex) { + Log.w(TAG, "mAcceptThread close error" + ex); + } + } + + if (mWakeLock != null) { + mWakeLock.release(); + mWakeLock = null; + } + + if (mServerSession != null) { + mServerSession.close(); + mServerSession = null; + } + + if (mBluetoothMnsObexClient != null) { + mBluetoothMnsObexClient.disconnect(); + mBluetoothMnsObexClient = null; + } + + closeConnectionSocket(); + if (VERBOSE) Log.v(TAG, "MAP Service closeService out"); } private final void startObexServerSession() throws IOException { - if (VERBOSE) Log.v(TAG, "Map Service startObexServerSession"); + if (DEBUG) Log.d(TAG, "Map Service startObexServerSession"); // acquire the wakeLock before start Obex transaction thread if (mWakeLock == null) { @@ -449,7 +303,7 @@ public class BluetoothMapService extends Service { } private void stopObexServerSession() { - if (VERBOSE) Log.v(TAG, "Map Service stopObexServerSession"); + if (DEBUG) Log.d(TAG, "MAP Service stopObexServerSession"); // Release the wake lock if obex transaction is over if (mWakeLock != null) { @@ -468,13 +322,8 @@ public class BluetoothMapService extends Service { mBluetoothMnsObexClient.disconnect(); mBluetoothMnsObexClient = null; } + closeConnectionSocket(); - try { - closeSocket(false, true); - mConnSocket = null; - } catch (IOException e) { - Log.e(TAG, "closeSocket error: " + e.toString()); - } // Last obex transaction is finished, we start to listen for incoming // connection again if (mAdapter.isEnabled()) { @@ -483,22 +332,7 @@ public class BluetoothMapService extends Service { setState(BluetoothMap.STATE_DISCONNECTED); } - private void notifyAuthKeyInput(final String key) { - synchronized (mAuth) { - if (key != null) { - mAuth.setSessionKey(key); - } - mAuth.setChallenged(true); - mAuth.notify(); - } - } - private void notifyAuthCancelled() { - synchronized (mAuth) { - mAuth.setCancelled(true); - mAuth.notify(); - } - } /** * A thread that runs in the background waiting for remote rfcomm @@ -512,35 +346,47 @@ public class BluetoothMapService extends Service { @Override public void run() { + BluetoothServerSocket serverSocket; if (mServerSocket == null) { if (!initSocket()) { - closeService(); return; } } while (!stopped) { try { - if (VERBOSE) Log.v(TAG, "Accepting socket connection..."); - mConnSocket = mServerSocket.accept(); - if (VERBOSE) Log.v(TAG, "Accepted socket connection..."); - - mRemoteDevice = mConnSocket.getRemoteDevice(); + if (DEBUG) Log.d(TAG, "Accepting socket connection..."); + serverSocket = mServerSocket; + if(serverSocket == null) { + Log.w(TAG, "mServerSocket is null"); + break; + } + mConnSocket = serverSocket.accept(); + if (DEBUG) Log.d(TAG, "Accepted socket connection..."); + synchronized (BluetoothMapService.this) { + if (mConnSocket == null) { + Log.w(TAG, "mConnSocket is null"); + break; + } + mRemoteDevice = mConnSocket.getRemoteDevice(); + } if (mRemoteDevice == null) { Log.i(TAG, "getRemoteDevice() = null"); break; } + sRemoteDeviceName = mRemoteDevice.getName(); // In case getRemoteName failed and return null if (TextUtils.isEmpty(sRemoteDeviceName)) { sRemoteDeviceName = getString(R.string.defaultname); } boolean trust = mRemoteDevice.getTrustState(); - if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust); + if (DEBUG) Log.d(TAG, "GetTrustState() = " + trust); + if (trust) { try { - if (VERBOSE) Log.v(TAG, "incoming connection accepted from: " + if (DEBUG) Log.d(TAG, "incoming connection accepted from: " + sRemoteDeviceName + " automatically as trusted device"); startObexServerSession(); } catch (IOException ex) { @@ -554,13 +400,10 @@ public class BluetoothMapService extends Service { intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); - intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName()); - intent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, - BluetoothMapReceiver.class.getName()); sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); isWaitingAuthorization = true; - if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: " + if (DEBUG) Log.d(TAG, "waiting for authorization for connection from: " + sRemoteDeviceName); } @@ -587,24 +430,17 @@ public class BluetoothMapService extends Service { case START_LISTENER: if (mAdapter.isEnabled()) { startRfcommSocketListener(); - } else { - closeService();// release all resources } break; case USER_TIMEOUT: - Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); + intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, + BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS); sendBroadcast(intent); isWaitingAuthorization = false; stopObexServerSession(); break; - case AUTH_TIMEOUT: - Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION); - sendBroadcast(i); - removeMapNotification(NOTIFICATION_ID_AUTH); - notifyAuthCancelled(); - break; case MSG_SERVERSESSION_CLOSE: stopObexServerSession(); break; @@ -613,10 +449,8 @@ public class BluetoothMapService extends Service { case MSG_SESSION_DISCONNECTED: // handled elsewhere break; - case MSG_OBEX_AUTH_CHALL: - createMapNotification(AUTH_CHALL_ACTION); - mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler - .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE); + case DISCONNECT_MAP: + disconnectMap((BluetoothDevice)msg.obj); break; default: break; @@ -624,6 +458,14 @@ public class BluetoothMapService extends Service { } }; + + public int getState() { + return mState; + } + + public BluetoothDevice getRemoteDevice() { + return mRemoteDevice; + } private void setState(int state) { setState(state, BluetoothMap.RESULT_SUCCESS); } @@ -634,9 +476,9 @@ public class BluetoothMapService extends Service { + result); int prevState = mState; mState = state; - Intent intent = new Intent(BluetoothMap.MAP_STATE_CHANGED_ACTION); - intent.putExtra(BluetoothMap.MAP_PREVIOUS_STATE, prevState); - intent.putExtra(BluetoothMap.MAP_STATE, mState); + Intent intent = new Intent(BluetoothMap.ACTION_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, mState); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); sendBroadcast(intent, BLUETOOTH_PERM); AdapterService s = AdapterService.getAdapterService(); @@ -647,132 +489,309 @@ public class BluetoothMapService extends Service { } } - private void createMapNotification(String action) { + public static String getRemoteDeviceName() { + return sRemoteDeviceName; + } - NotificationManager nm = (NotificationManager) - getSystemService(Context.NOTIFICATION_SERVICE); + public boolean disconnect(BluetoothDevice device) { + mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(DISCONNECT_MAP, 0, 0, device)); + return true; + } - // Create an intent triggered by clicking on the status icon. - Intent clickIntent = new Intent(); - clickIntent.setClass(this, BluetoothMapActivity.class); - clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - clickIntent.setAction(action); + public boolean disconnectMap(BluetoothDevice device) { + boolean result = false; + if (DEBUG) Log.d(TAG, "disconnectMap"); + if (getRemoteDevice().equals(device)) { + switch (mState) { + case BluetoothMap.STATE_CONNECTED: + if (mServerSession != null) { + mServerSession.close(); + mServerSession = null; + } + if(mBluetoothMnsObexClient != null) { + mBluetoothMnsObexClient.disconnect(); //FIXME should use shutdown when implemented + mBluetoothMnsObexClient = null; + } + closeConnectionSocket(); - // Create an intent triggered by clicking on the - // "Clear All Notifications" button - Intent deleteIntent = new Intent(); - deleteIntent.setClass(this, BluetoothMapReceiver.class); + setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); + result = true; + break; + default: + break; + } + } + return result; + } - Notification notification = null; - String name = getRemoteDeviceName(); + public List<BluetoothDevice> getConnectedDevices() { + List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); + synchronized(this) { + if (mState == BluetoothMap.STATE_CONNECTED && mRemoteDevice != null) { + devices.add(mRemoteDevice); + } + } + return devices; + } - if (action.equals(AUTH_CHALL_ACTION)) { - deleteIntent.setAction(AUTH_CANCELLED_ACTION); - notification = new Notification.Builder(this) - .setContentTitle(getString(R.string.auth_notif_title)) - .setContentText(getString(R.string.auth_notif_message,name)) - .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) - .build(); + public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(); + Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); + int connectionState; + synchronized (this) { + for (BluetoothDevice device : bondedDevices) { + ParcelUuid[] featureUuids = device.getUuids(); + if (!BluetoothUuid.containsAnyUuid(featureUuids, MAP_UUIDS)) { + continue; + } + connectionState = getConnectionState(device); + for(int i = 0; i < states.length; i++) { + if (connectionState == states[i]) { + deviceList.add(device); + } + } + } + } + return deviceList; + } - notification.flags |= Notification.FLAG_AUTO_CANCEL; - notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE; - notification.defaults = Notification.DEFAULT_SOUND; - notification.deleteIntent = PendingIntent.getBroadcast(this, 0, deleteIntent, 0); - nm.notify(NOTIFICATION_ID_AUTH, notification); + public int getConnectionState(BluetoothDevice device) { + synchronized(this) { + if (getState() == BluetoothMap.STATE_CONNECTED && getRemoteDevice().equals(device)) { + return BluetoothProfile.STATE_CONNECTED; + } else { + return BluetoothProfile.STATE_DISCONNECTED; + } } } - private void removeMapNotification(int id) { - NotificationManager nm = (NotificationManager) - getSystemService(Context.NOTIFICATION_SERVICE); - nm.cancel(id); + public boolean setPriority(BluetoothDevice device, int priority) { + Settings.Global.putInt(getContentResolver(), + Settings.Global.getBluetoothMapPriorityKey(device.getAddress()), + priority); + if (DEBUG) Log.d(TAG, "Saved priority " + device + " = " + priority); + return true; } - public static String getRemoteDeviceName() { - return sRemoteDeviceName; + public int getPriority(BluetoothDevice device) { + int priority = Settings.Global.getInt(getContentResolver(), + Settings.Global.getBluetoothMapPriorityKey(device.getAddress()), + BluetoothProfile.PRIORITY_UNDEFINED); + return priority; } - /** - * Handlers for incoming service calls - */ - private final IBluetoothMap.Stub mBinder = new IBluetoothMap.Stub() { - public int getState() { - if (DEBUG) Log.d(TAG, "getState " + mState); + @Override + protected IProfileServiceBinder initBinder() { + return new BluetoothMapBinder(this); + } - if (!Utils.checkCaller()) { - Log.w(TAG,"getState(): not allowed for non-active user"); - return BluetoothMap.STATE_DISCONNECTED; - } + @Override + protected boolean start() { + if (DEBUG) Log.d(TAG, "start()"); + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); + filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); + try { + registerReceiver(mMapReceiver, filter); + } catch (Exception e) { + Log.w(TAG,"Unable to register map receiver",e); + } + mInterrupted = false; + mAdapter = BluetoothAdapter.getDefaultAdapter(); + // start RFCOMM listener + mSessionStatusHandler.sendMessage(mSessionStatusHandler + .obtainMessage(START_LISTENER)); + return true; + } - enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - return mState; + @Override + protected boolean stop() { + if (DEBUG) Log.d(TAG, "stop()"); + try { + unregisterReceiver(mMapReceiver); + } catch (Exception e) { + Log.w(TAG,"Unable to unregister map receiver",e); } - public BluetoothDevice getClient() { - if (DEBUG) Log.d(TAG, "getClient" + mRemoteDevice); + setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); + closeService(); + if(mSessionStatusHandler != null) { + mSessionStatusHandler.removeCallbacksAndMessages(null); + } + isWaitingAuthorization = false; + return true; + } + + public boolean cleanup() { + if (DEBUG) Log.d(TAG, "cleanup()"); + setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); + closeService(); + if(mSessionStatusHandler != null) { + mSessionStatusHandler.removeCallbacksAndMessages(null); + } + isWaitingAuthorization = false; + return true; + } + + private MapBroadcastReceiver mMapReceiver = new MapBroadcastReceiver(); + private class MapBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) Log.d(TAG, "onReceive"); + String action = intent.getAction(); + if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.ERROR); + if (state == BluetoothAdapter.STATE_TURNING_OFF) { + if (DEBUG) Log.d(TAG, "STATE_TURNING_OFF"); + // Release all resources + closeService(); + isWaitingAuthorization = false; + } else if (state == BluetoothAdapter.STATE_ON) { + if (DEBUG) Log.d(TAG, "STATE_ON"); + mInterrupted = false; + // start RFCOMM listener + mSessionStatusHandler.sendMessage(mSessionStatusHandler + .obtainMessage(START_LISTENER)); + } + } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { + int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, + BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); + if (DEBUG) Log.d(TAG, "Received ACTION_CONNECTION_ACCESS_REPLY:" + + requestType + ":" + isWaitingAuthorization); + if ((!isWaitingAuthorization) || + (requestType != BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS)) { + // this reply is not for us + return; + } + + isWaitingAuthorization = false; + + if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, + BluetoothDevice.CONNECTION_ACCESS_NO) == + BluetoothDevice.CONNECTION_ACCESS_YES) { + //bluetooth connection accepted by user + if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) { + boolean result = mRemoteDevice.setTrust(true); + if (DEBUG) Log.d(TAG, "setTrust() result=" + result); + } + try { + if (mConnSocket != null) { + // start obex server and rfcomm connection + startObexServerSession(); + } else { + stopObexServerSession(); + } + } catch (IOException ex) { + Log.e(TAG, "Caught the error: " + ex.toString()); + } + } else { + stopObexServerSession(); + } + } + } + }; + + //Binder object: Must be static class or memory leak may occur + /** + * This class implements the IBluetoothMap interface - or actually it validates the + * preconditions for calling the actual functionality in the MapService, and calls it. + */ + private static class BluetoothMapBinder extends IBluetoothMap.Stub + implements IProfileServiceBinder { + private BluetoothMapService mService; + + private BluetoothMapService getService() { if (!Utils.checkCaller()) { - Log.w(TAG,"getClient(): not allowed for non-active user"); + Log.w(TAG,"MAP call not allowed for non-active user"); return null; } - enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - if (mState == BluetoothMap.STATE_DISCONNECTED) { - return null; + if (mService != null && mService.isAvailable()) { + mService.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + return mService; } - return mRemoteDevice; + return null; } - public boolean isConnected(BluetoothDevice device) { - if (!Utils.checkCaller()) { - Log.w(TAG,"isConnected(): not allowed for non-active user"); - return false; - } + BluetoothMapBinder(BluetoothMapService service) { + if (VERBOSE) Log.v(TAG, "BluetoothMapBinder()"); + mService = service; + } - enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - return mState == BluetoothMap.STATE_CONNECTED && mRemoteDevice.equals(device); + public boolean cleanup() { + mService = null; + return true; } - public boolean connect(BluetoothDevice device) { - if (!Utils.checkCaller()) { - Log.w(TAG,"connect(): not allowed for non-active user"); - return false; - } + public int getState() { + if (VERBOSE) Log.v(TAG, "getState()"); + BluetoothMapService service = getService(); + if (service == null) return BluetoothMap.STATE_DISCONNECTED; + return getService().getState(); + } + + public BluetoothDevice getClient() { + if (VERBOSE) Log.v(TAG, "getClient()"); + BluetoothMapService service = getService(); + if (service == null) return null; + Log.v(TAG, "getClient() - returning " + service.getRemoteDevice()); + return service.getRemoteDevice(); + } + + public boolean isConnected(BluetoothDevice device) { + if (VERBOSE) Log.v(TAG, "isConnected()"); + BluetoothMapService service = getService(); + if (service == null) return false; + return service.getState() == BluetoothMap.STATE_CONNECTED && service.getRemoteDevice().equals(device); + } - enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); + public boolean connect(BluetoothDevice device) { + if (VERBOSE) Log.v(TAG, "connect()"); + BluetoothMapService service = getService(); + if (service == null) return false; return false; } - public void disconnect() { - if (DEBUG) Log.d(TAG, "disconnect"); + public boolean disconnect(BluetoothDevice device) { + if (VERBOSE) Log.v(TAG, "disconnect()"); + BluetoothMapService service = getService(); + if (service == null) return false; + return service.disconnect(device); + } - if (!Utils.checkCaller()) { - Log.w(TAG,"disconnect(): not allowed for non-active user"); - return; - } + public List<BluetoothDevice> getConnectedDevices() { + if (VERBOSE) Log.v(TAG, "getConnectedDevices()"); + BluetoothMapService service = getService(); + if (service == null) return new ArrayList<BluetoothDevice>(0); + return service.getConnectedDevices(); + } - enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - synchronized (BluetoothMapService.this) { - switch (mState) { - case BluetoothMap.STATE_CONNECTED: - if (mServerSession != null) { - mServerSession.close(); - mServerSession = null; - } - try { - closeSocket(false, true); - mConnSocket = null; - } catch (IOException ex) { - Log.e(TAG, "Caught the error: " + ex); - } - setState(BluetoothMap.STATE_DISCONNECTED, BluetoothMap.RESULT_CANCELED); - break; - default: - break; - } - } + public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + if (VERBOSE) Log.v(TAG, "getDevicesMatchingConnectionStates()"); + BluetoothMapService service = getService(); + if (service == null) return new ArrayList<BluetoothDevice>(0); + return service.getDevicesMatchingConnectionStates(states); + } + + public int getConnectionState(BluetoothDevice device) { + if (VERBOSE) Log.v(TAG, "getConnectionState()"); + BluetoothMapService service = getService(); + if (service == null) return BluetoothProfile.STATE_DISCONNECTED; + return service.getConnectionState(device); + } + + public boolean setPriority(BluetoothDevice device, int priority) { + BluetoothMapService service = getService(); + if (service == null) return false; + return service.setPriority(device, priority); + } + + public int getPriority(BluetoothDevice device) { + BluetoothMapService service = getService(); + if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED; + return service.getPriority(device); } }; } diff --git a/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java b/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java index 7f87cd76b..2070c2693 100644 --- a/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java +++ b/src/com/android/bluetooth/map/BluetoothMapSmsPdu.java @@ -46,7 +46,7 @@ import com.android.internal.telephony.gsm.SmsMessage.SubmitPdu; public class BluetoothMapSmsPdu { private static final String TAG = "BluetoothMapSmsPdu"; - private static final boolean V = true; + private static final boolean V = false; private static int INVALID_VALUE = -1; public static int SMS_TYPE_GSM = 1; public static int SMS_TYPE_CDMA = 2; @@ -247,7 +247,6 @@ public class BluetoothMapSmsPdu { data[offset+2] = (byte) tmp; // } - //TODO: Error handling. /* TODO: Do we need to change anything in the user data? Not sure if the user data is * just encoded using GSM encoding, or it is an actual GSM submit PDU embedded * in the user data? @@ -258,16 +257,16 @@ public class BluetoothMapSmsPdu { private static final byte TP_MIT_DELIVER = 0x00; // bit 0 and 1 private static final byte TP_MMS_NO_MORE = 0x04; // bit 2 private static final byte TP_RP_NO_REPLY_PATH = 0x00; // bit 7 - private static final byte TP_UDHI_MASK = 0x20; // bit 6 + private static final byte TP_UDHI_MASK = 0x40; // bit 6 private static final byte TP_SRI_NO_REPORT = 0x00; // bit 5 private int gsmSubmitGetTpPidOffset() { - /* calculate the offset to TP_PID and return the TP_PID byte. + /* calculate the offset to TP_PID. * The TP-DA has variable length, and the length excludes the 2 byte length and type headers. * The TP-DA is two bytes within the PDU */ - int offset = 2 + (data[2] & 0xff) + 2; // - if((offset > data.length) || (offset > (2 + 12))) // max length of TP_DA is 12 bytes + two byte offset - throw new IllegalArgumentException("wrongly formatted gsm submit PDU"); + int offset = 2 + ((data[2]+1) & 0xff)/2 + 2; // data[2] is the number of semi-octets in the phone number (ceil result) + if((offset > data.length) || (offset > (2 + 12))) // max length of TP_DA is 12 bytes + two byte offset. + throw new IllegalArgumentException("wrongly formatted gsm submit PDU. offset = " + offset); return offset; } @@ -304,37 +303,54 @@ public class BluetoothMapSmsPdu { pdu.skip(gsmSubmitGetTpUdlOffset()); int userDataLength = pdu.read(); - int userDataHeaderLength = pdu.read(); - - // This part is only needed to extract the language info, hence only needed for 7 bit encoding - if(encoding == SmsConstants.ENCODING_7BIT) - { - byte[] udh = new byte[userDataHeaderLength]; - try { - pdu.read(udh); - } catch (IOException e) { - Log.w(TAG, "unable to read userDataHeader", e); + if(gsmSubmitHasUserDataHeader() == true) { + int userDataHeaderLength = pdu.read(); + + // This part is only needed to extract the language info, hence only needed for 7 bit encoding + if(encoding == SmsConstants.ENCODING_7BIT) + { + byte[] udh = new byte[userDataHeaderLength]; + try { + pdu.read(udh); + } catch (IOException e) { + Log.w(TAG, "unable to read userDataHeader", e); + } + SmsHeader userDataHeader = SmsHeader.fromByteArray(udh); + languageTable = userDataHeader.languageTable; + languageShiftTable = userDataHeader.languageShiftTable; + + int headerBits = (userDataHeaderLength + 1) * 8; + int headerSeptets = headerBits / 7; + headerSeptets += (headerBits % 7) > 0 ? 1 : 0; + userDataSeptetPadding = (headerSeptets * 7) - headerBits; + msgSeptetCount = userDataLength - headerSeptets; } - SmsHeader userDataHeader = SmsHeader.fromByteArray(udh); - languageTable = userDataHeader.languageTable; - languageShiftTable = userDataHeader.languageShiftTable; - - int headerBits = (userDataHeaderLength + 1) * 8; - int headerSeptets = headerBits / 7; - headerSeptets += (headerBits % 7) > 0 ? 1 : 0; - userDataSeptetPadding = (headerSeptets * 7) - headerBits; - msgSeptetCount = userDataLength - headerSeptets; + userDataMsgOffset = gsmSubmitGetTpUdOffset() + userDataHeaderLength + 1; // Add the byte containing the length + } + else + { + userDataSeptetPadding = 0; + msgSeptetCount = userDataLength; + userDataMsgOffset = gsmSubmitGetTpUdOffset(); + } + if(V) { + Log.v(TAG, "encoding:" + encoding); + Log.v(TAG, "msgSeptetCount:" + msgSeptetCount); + Log.v(TAG, "userDataSeptetPadding:" + userDataSeptetPadding); + Log.v(TAG, "languageShiftTable:" + languageShiftTable); + Log.v(TAG, "languageTable:" + languageTable); + Log.v(TAG, "userDataMsgOffset:" + userDataMsgOffset); } - userDataMsgOffset = gsmSubmitGetTpUdOffset() + userDataHeaderLength + 1; // Add the byte containing the length } private void gsmWriteDate(ByteArrayOutputStream header, long time) throws UnsupportedEncodingException { SimpleDateFormat format = new SimpleDateFormat("yyMMddHHmmss"); Date date = new Date(time); String timeStr = format.format(date); // Format to YYMMDDTHHMMSS UTC time + if(V) Log.v(TAG, "Generated time string: " + timeStr); byte[] timeChars = timeStr.getBytes("US-ASCII"); - for(int i = 0, n = timeStr.length()/2; i < n; i++) { + for(int i = 0, n = timeStr.length(); i < n; i+=2) { header.write((timeChars[i+1]-0x30) << 4 | (timeChars[i]-0x30)); // Offset from ascii char to decimal value } @@ -379,8 +395,16 @@ public class BluetoothMapSmsPdu { newPdu.write(TP_MIT_DELIVER | TP_MMS_NO_MORE | TP_RP_NO_REPLY_PATH | TP_SRI_NO_REPORT | (data[0] & 0xff) & TP_UDHI_MASK); encodedAddress = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(originator); - // Insert originator address into the header - this includes the length - newPdu.write(encodedAddress); + if(encodedAddress != null) { + int padding = (encodedAddress[encodedAddress.length-1] & 0xf0) == 0xf0 ? 1 : 0; + encodedAddress[0] = (byte)((encodedAddress[0]-1)*2 - padding); // Convert from octet length to semi octet length + // Insert originator address into the header - this includes the length + newPdu.write(encodedAddress); + } else { + newPdu.write(0); /* zero length */ + newPdu.write(0x81); /* International type */ + } + newPdu.write(data[gsmSubmitGetTpPidOffset()]); newPdu.write(data[gsmSubmitGetTpDcsOffset()]); // Generate service center time stamp @@ -391,7 +415,7 @@ public class BluetoothMapSmsPdu { newPdu.write(data, gsmSubmitGetTpUdOffset(), data.length - gsmSubmitGetTpUdOffset()); } catch (IOException e) { Log.e(TAG, "", e); - throw new IllegalArgumentException("Failed to change type to deliver PDU."); // TODO: Is this the best way to handle this error? - which cannot occur... + throw new IllegalArgumentException("Failed to change type to deliver PDU."); } data = newPdu.toByteArray(); } @@ -476,53 +500,55 @@ public class BluetoothMapSmsPdu { newPdu = new SmsPdu(data, encoding, phoneType, languageTable); pdus.add(newPdu); } - - /* This code is a reduced copy of the actual code used in the Android SMS sub system, - * hence the comments have been left untouched. */ - for(int i = 0; i < msgCount; i++){ - SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); - concatRef.refNumber = refNumber; - concatRef.seqNumber = i + 1; // 1-based sequence - concatRef.msgCount = msgCount; - // TODO: We currently set this to true since our messaging app will never - // send more than 255 parts (it converts the message to MMS well before that). - // However, we should support 3rd party messaging apps that might need 16-bit - // references - // Note: It's not sufficient to just flip this bit to true; it will have - // ripple effects (several calculations assume 8-bit ref). - concatRef.isEightBits = true; - SmsHeader smsHeader = new SmsHeader(); - smsHeader.concatRef = concatRef; - - /* Depending on the type, call either GSM or CDMA getSubmitPdu(). The encoding - * will be determined(again) by getSubmitPdu(). - * All packets need to be encoded using the same encoding, as the bMessage - * only have one filed to describe the encoding for all messages in a concatenated - * SMS... */ - if (encoding == SmsConstants.ENCODING_7BIT) { - smsHeader.languageTable = languageTable; - smsHeader.languageShiftTable = languageShiftTable; - } - - if(phoneType == SMS_TYPE_GSM){ - data = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null, destinationAddress, - smsFragments.get(i), false, SmsHeader.toByteArray(smsHeader), - encoding, languageTable, languageShiftTable).encodedMessage; - } else { // SMS_TYPE_CDMA - UserData uData = new UserData(); - uData.payloadStr = smsFragments.get(i); - uData.userDataHeader = smsHeader; + else + { + /* This code is a reduced copy of the actual code used in the Android SMS sub system, + * hence the comments have been left untouched. */ + for(int i = 0; i < msgCount; i++){ + SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef(); + concatRef.refNumber = refNumber; + concatRef.seqNumber = i + 1; // 1-based sequence + concatRef.msgCount = msgCount; + // We currently set this to true since our messaging app will never + // send more than 255 parts (it converts the message to MMS well before that). + // However, we should support 3rd party messaging apps that might need 16-bit + // references + // Note: It's not sufficient to just flip this bit to true; it will have + // ripple effects (several calculations assume 8-bit ref). + concatRef.isEightBits = true; + SmsHeader smsHeader = new SmsHeader(); + smsHeader.concatRef = concatRef; + + /* Depending on the type, call either GSM or CDMA getSubmitPdu(). The encoding + * will be determined(again) by getSubmitPdu(). + * All packets need to be encoded using the same encoding, as the bMessage + * only have one filed to describe the encoding for all messages in a concatenated + * SMS... */ if (encoding == SmsConstants.ENCODING_7BIT) { - uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET; - } else { // assume UTF-16 - uData.msgEncoding = UserData.ENCODING_UNICODE_16; + smsHeader.languageTable = languageTable; + smsHeader.languageShiftTable = languageShiftTable; + } + + if(phoneType == SMS_TYPE_GSM){ + data = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null, destinationAddress, + smsFragments.get(i), false, SmsHeader.toByteArray(smsHeader), + encoding, languageTable, languageShiftTable).encodedMessage; + } else { // SMS_TYPE_CDMA + UserData uData = new UserData(); + uData.payloadStr = smsFragments.get(i); + uData.userDataHeader = smsHeader; + if (encoding == SmsConstants.ENCODING_7BIT) { + uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET; + } else { // assume UTF-16 + uData.msgEncoding = UserData.ENCODING_UNICODE_16; + } + uData.msgEncodingSet = true; + data = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(destinationAddress, + uData, false).encodedMessage; } - uData.msgEncodingSet = true; - data = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(destinationAddress, - uData, false).encodedMessage; + newPdu = new SmsPdu(data, encoding, phoneType, languageTable); + pdus.add(newPdu); } - newPdu = new SmsPdu(data, encoding, phoneType, languageTable); - pdus.add(newPdu); } return pdus; @@ -589,7 +615,7 @@ public class BluetoothMapSmsPdu { /* The format of a native GSM SMS is: <sc-address><pdu> where sc-address is: * <length-byte><type-byte><number-bytes> */ int addressLength = data[0] & 0xff; // Treat the byte value as an unsigned value - if(addressLength >= data.length) // TODO: We could verify that the address-length is no longer than 11 bytes + if(addressLength >= data.length) // We could verify that the address-length is no longer than 11 bytes throw new IllegalArgumentException("Length of address exeeds the length of the PDU data."); int pduLength = data.length-(1+addressLength); byte[] newData = new byte[pduLength]; @@ -683,14 +709,13 @@ public class BluetoothMapSmsPdu { /* TODO: This is NOT good design - to have the pdu class being depending on these two function calls. * - move the encoding extraction into the pdu class */ pdu.setEncoding(encodingType); - if(pdu.gsmSubmitHasUserDataHeader()) { - pdu.gsmDecodeUserDataHeader(); - } + pdu.gsmDecodeUserDataHeader(); try { switch (encodingType) { case SmsConstants.ENCODING_UNKNOWN: case SmsConstants.ENCODING_8BIT: + Log.w(TAG, "Unknown encoding type: " + encodingType); messageBody = null; break; @@ -698,16 +723,18 @@ public class BluetoothMapSmsPdu { messageBody = GsmAlphabet.gsm7BitPackedToString(pdu.getData(), pdu.getUserDataMsgOffset(), pdu.getMsgSeptetCount(), pdu.getUserDataSeptetPadding(), pdu.getLanguageTable(), pdu.getLanguageShiftTable()); + Log.i(TAG, "Decoded as 7BIT: " + messageBody); break; case SmsConstants.ENCODING_16BIT: messageBody = new String(pdu.getData(), pdu.getUserDataMsgOffset(), pdu.getUserDataMsgSize(), "utf-16"); + Log.i(TAG, "Decoded as 16BIT: " + messageBody); break; case SmsConstants.ENCODING_KSC5601: messageBody = new String(pdu.getData(), pdu.getUserDataMsgOffset(), pdu.getUserDataMsgSize(), "KSC5601"); - + Log.i(TAG, "Decoded as KSC5601: " + messageBody); break; } } catch (UnsupportedEncodingException e) { diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessage.java b/src/com/android/bluetooth/map/BluetoothMapbMessage.java index 084ebedfe..de3de663e 100644 --- a/src/com/android/bluetooth/map/BluetoothMapbMessage.java +++ b/src/com/android/bluetooth/map/BluetoothMapbMessage.java @@ -15,20 +15,26 @@ package com.android.bluetooth.map; import java.io.ByteArrayOutputStream; +import java.io.File; +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.util.ArrayList; +import android.os.Environment; import android.telephony.PhoneNumberUtils; import android.util.Log; + import com.android.bluetooth.map.BluetoothMapUtils.TYPE; public abstract class BluetoothMapbMessage { protected static String TAG = "BluetoothMapbMessage"; - protected static final boolean D = true; - protected static final boolean V = true; + protected static final boolean D = false; + protected static final boolean V = false; private static final String VERSION = "VERSION:1.0"; public static int INVALID_VALUE = -1; @@ -155,17 +161,17 @@ public abstract class BluetoothMapbMessage { { sb.append("BEGIN:VCARD").append("\r\n"); sb.append("VERSION:").append(version).append("\r\n"); - if (version.equals("3.0") && formattedName != null) + if(version.equals("3.0") && formattedName != null) { sb.append("FN:").append(formattedName).append("\r\n"); } if (name != null) sb.append("N:").append(name).append("\r\n"); - for (String phoneNumber : phoneNumbers) + for(String phoneNumber : phoneNumbers) { sb.append("TEL:").append(phoneNumber).append("\r\n"); } - for (String emailAddress : emailAddresses) + for(String emailAddress : emailAddresses) { sb.append("EMAIL:").append(emailAddress).append("\r\n"); } @@ -297,10 +303,11 @@ public abstract class BluetoothMapbMessage { * @return the next line */ public String getLineEnforce() { - String line = getLine(); - if (line == null) - throw new IllegalArgumentException("Bmessage too short"); - return line; + String line = getLine(); + if (line == null) + throw new IllegalArgumentException("Bmessage too short"); + + return line; } @@ -315,8 +322,9 @@ public abstract class BluetoothMapbMessage { */ public void expect(String subString) throws IllegalArgumentException{ String line = getLine(); - if (!line.contains(subString)) - // TODO: Should this be case insensitive? (Either use toUpper() or matches()) + if(line == null || subString == null){ + throw new IllegalArgumentException("Line or substring is null"); + }else if(!line.toUpperCase().contains(subString.toUpperCase())) throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\""); } @@ -329,9 +337,9 @@ public abstract class BluetoothMapbMessage { */ public void expect(String subString, String subString2) throws IllegalArgumentException{ String line = getLine(); - if(!line.contains(subString)) // TODO: Should this be case insensitive? (Either use toUpper() or matches()) + if(!line.toUpperCase().contains(subString.toUpperCase())) throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\""); - if(!line.contains(subString2)) // TODO: Should this be case insensitive? (Either use toUpper() or matches()) + if(!line.toUpperCase().contains(subString2.toUpperCase())) throw new IllegalArgumentException("Expected \"" + subString + "\" in: \"" + line + "\""); } @@ -346,7 +354,7 @@ public abstract class BluetoothMapbMessage { try { int bytesRead; int offset=0; - while ((bytesRead = mInStream.read(data, offset, length-offset)) != length) { + while ((bytesRead = mInStream.read(data, offset, length-offset)) != (length - offset)) { if(bytesRead == -1) return null; offset += bytesRead; @@ -359,21 +367,85 @@ public abstract class BluetoothMapbMessage { } }; - public BluetoothMapbMessage() { + public BluetoothMapbMessage(){ } public static BluetoothMapbMessage parse(InputStream bMsgStream, int appParamCharset) throws IllegalArgumentException{ - BMsgReader reader = new BMsgReader(bMsgStream); + BMsgReader reader; String line = ""; BluetoothMapbMessage newBMsg = null; - reader.expect("BEGIN:BMSG"); - reader.expect("VERSION","1.0"); boolean status = false; boolean statusFound = false; TYPE type = null; String folder = null; + /* This section is used for debug. It will write the incoming message to a file on the SD-card, + * hence should only be used for test/debug. + * If an error occurs, it will result in a OBEX_HTTP_PRECON_FAILED to be send to the client, + * even though the message might be formatted correctly, hence only enable this code for test. */ + if(V) { + /* Read the entire stream into a file on the SD card*/ + File sdCard = Environment.getExternalStorageDirectory(); + File dir = new File (sdCard.getAbsolutePath() + "/bluetooth/log/"); + dir.mkdirs(); + File file = new File(dir, "receivedBMessage.txt"); + FileOutputStream outStream = null; + boolean failed = false; + int writtenLen = 0; + + try { + outStream = new FileOutputStream(file, false); /* overwrite if it does already exist */ + + byte[] buffer = new byte[4*1024]; + int len = 0; + while ((len = bMsgStream.read(buffer)) > 0) { + outStream.write(buffer, 0, len); + writtenLen += len; + } + } catch (FileNotFoundException e) { + Log.e(TAG,"Unable to create output stream",e); + } catch (IOException e) { + Log.e(TAG,"Failed to copy the received message",e); + if(writtenLen != 0) + failed = true; /* We failed to write the complete file, hence the received message is lost... */ + } finally { + if(outStream != null) + try { + outStream.close(); + } catch (IOException e) { + } + } + + /* Return if we corrupted the incoming bMessage. */ + if(failed) { + throw new IllegalArgumentException(); /* terminate this function with an error. */ + } + + if (outStream == null) { + /* We failed to create the the log-file, just continue using the original bMsgStream. */ + } else { + /* overwrite the bMsgStream using the file written to the SD-Card */ + try { + bMsgStream.close(); + } catch (IOException e) { + /* Ignore if we cannot close the stream. */ + } + /* Open the file and overwrite bMsgStream to read from the file */ + try { + bMsgStream = new FileInputStream(file); + } catch (FileNotFoundException e) { + Log.e(TAG,"Failed to open the bMessage file", e); + throw new IllegalArgumentException(); /* terminate this function with an error. */ + } + } + Log.i(TAG, "The incoming bMessage have been dumped to " + file.getAbsolutePath()); + } /* End of if(V) log-section */ + + reader = new BMsgReader(bMsgStream); + reader.expect("BEGIN:BMSG"); + reader.expect("VERSION","1.0"); + line = reader.getLineEnforce(); // Parse the properties - which end with either a VCARD or a BENV while(!line.contains("BEGIN:VCARD") && !line.contains("BEGIN:BENV")) { @@ -420,9 +492,8 @@ public abstract class BluetoothMapbMessage { String[] arg = line.split(":"); if (arg != null && arg.length == 2) { folder = arg[1].trim(); - } else { - throw new IllegalArgumentException("Missing value for 'FOLDER':" + line); } + // This can be empty for push message - hence ignore if there is no value } line = reader.getLineEnforce(); } @@ -431,7 +502,7 @@ public abstract class BluetoothMapbMessage { newBMsg.setType(type); newBMsg.appParamCharset = appParamCharset; if(folder != null) - newBMsg.setFolder(folder); + newBMsg.setCompleteFolder(folder); if(statusFound) newBMsg.setStatus(status); @@ -449,6 +520,13 @@ public abstract class BluetoothMapbMessage { /* TODO: Do we need to validate the END:* tags? They are only needed if someone puts additional info * below the END:MSG - in which case we don't handle it. */ + + try { + bMsgStream.close(); + } catch (IOException e) { + /* Ignore if we cannot close the stream. */ + } + return newBMsg; } @@ -563,7 +641,7 @@ public abstract class BluetoothMapbMessage { String messages[] = data.split("\r\nEND:MSG\r\n"); parseMsgInit(); for(int i = 0; i < messages.length; i++) { - messages[i] = messages[i].replaceFirst("^BEGIN:MGS\r\n", ""); + messages[i] = messages[i].replaceFirst("^BEGIN:MSG\r\n", ""); messages[i] = messages[i].replaceAll("\r\n([/]*)/END\\:MSG", "\r\n$1END:MSG"); messages[i] = messages[i].trim(); parseMsgPart(messages[i]); @@ -604,10 +682,19 @@ public abstract class BluetoothMapbMessage { return type; } + public void setCompleteFolder(String folder) { + this.folder = folder; + } + public void setFolder(String folder) { this.folder = "telecom/msg/" + folder; } + public String getFolder() { + return folder; + } + + public void setEncoding(String encoding) { this.encoding = encoding; } @@ -704,8 +791,16 @@ public abstract class BluetoothMapbMessage { if(D) Log.d(TAG,"Decoding binary data: START:" + data + ":END"); for(int i = 0, j = 0, n = out.length; i < n; i++) { - value = data.substring(j++, j++); // same as data.substring(2*i, 2*i+1) - out[i] = Byte.valueOf(value, 16); + value = data.substring(j++, ++j); // same as data.substring(2*i, 2*i+1+1) - substring() uses end-1 for last index + out[i] = (byte)(Integer.valueOf(value, 16) & 0xff); + } + if(D) { + StringBuilder sb = new StringBuilder(out.length); + for(int i = 0, n = out.length; i < n; i++) + { + sb.append(String.format("%02X",out[i] & 0xff)); + } + Log.d(TAG,"Decoded binary data: START:" + sb.toString() + ":END"); } return out; } @@ -718,7 +813,10 @@ public abstract class BluetoothMapbMessage { sb.append(VERSION).append("\r\n"); sb.append("STATUS:").append(status).append("\r\n"); sb.append("TYPE:").append(type.name()).append("\r\n"); - sb.append("FOLDER:").append(folder).append("\r\n"); + if(folder.length() > 512) + sb.append("FOLDER:").append(folder.substring(folder.length()-512, folder.length())).append("\r\n"); + else + sb.append("FOLDER:").append(folder).append("\r\n"); if(originator != null){ for(vCard element : originator) element.encode(sb); diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java b/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java index 169c42dc4..507290d65 100644 --- a/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java +++ b/src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java @@ -30,37 +30,40 @@ import android.util.Log; public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { - private String mmsBody; - /** - * TODO: Determine the best way to store the MMS message content. - * @param mmsBody - */ - public static class MimePart { public long _id = INVALID_VALUE; /* The _id from the content provider, can be used to sort the parts if needed */ public String contentType = null; /* The mime type, e.g. text/plain */ public String contentId = null; + public String contentLocation = null; + public String contentDisposition = null; public String partName = null; /* e.g. text_1.txt*/ public String charsetName = null; /* This seems to be a number e.g. 106 for UTF-8 CharacterSets holds a method for the mapping. */ public String fileName = null; /* Do not seem to be used */ public byte[] data = null; /* The raw un-encoded data e.g. the raw jpeg data or the text.getBytes("utf-8") */ + + public void encode(StringBuilder sb, String boundaryTag, boolean last) throws UnsupportedEncodingException { - sb.append("--").append(boundaryTag); - if(last) - sb.append("--"); - sb.append("\r\n"); + sb.append("--").append(boundaryTag).append("\r\n"); if(contentType != null) sb.append("Content-Type: ").append(contentType); if(charsetName != null) sb.append("; ").append("charset=\"").append(charsetName).append("\""); sb.append("\r\n"); - if(partName != null) - sb.append("Content-Location: ").append(partName).append("\r\n"); + if(contentLocation != null) + sb.append("Content-Location: ").append(contentLocation).append("\r\n"); + if(contentId != null) + sb.append("Content-ID: ").append(contentId).append("\r\n"); + if(contentDisposition != null) + sb.append("Content-Disposition: ").append(contentDisposition).append("\r\n"); if(data != null) { - // If errata 4176 is adopted in the current form, the below is not allowed, Base64 should be used for text - if(contentType.toUpperCase().contains("TEXT")) { + /* TODO: If errata 4176 is adopted in the current form (it is not in either 1.1 or 1.2), + the below is not allowed, Base64 should be used for text. */ + + if(contentType != null && + (contentType.toUpperCase().contains("TEXT") || + contentType.toUpperCase().contains("SMIL") )) { sb.append("Content-Transfer-Encoding: 8BIT\r\n\r\n"); // Add the header split empty line sb.append(new String(data,"UTF-8")).append("\r\n"); } @@ -69,8 +72,26 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { sb.append(Base64.encodeToString(data, Base64.DEFAULT)).append("\r\n"); } } + if(last) { + sb.append("--").append(boundaryTag).append("--").append("\r\n"); + } + } + + public void encodePlainText(StringBuilder sb) throws UnsupportedEncodingException { + if(contentType != null && contentType.toUpperCase().contains("TEXT")) { + sb.append(new String(data,"UTF-8")).append("\r\n"); + } else if(contentType != null && contentType.toUpperCase().contains("/SMIL")) { + /* Skip the smil.xml, as no-one knows what it is. */ + } else { + /* Not a text part, just print the filename or part name if they exist. */ + if(partName != null) + sb.append("<").append(partName).append(">\r\n"); + else + sb.append("<").append("attachment").append(">\r\n"); + } } } + private long date = INVALID_VALUE; private String subject = null; private ArrayList<Rfc822Token> from = null; // Shall not be empty @@ -83,10 +104,11 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { private ArrayList<MimePart> parts = null; private String contentType = null; private String boundary = null; + private boolean textOnly = false; + private boolean includeAttachments; + private boolean hasHeaders = false; + private String encoding = null; - /* TODO: - * - create an encoder for the parts e.g. embedded in the mimePart class - * */ private String getBoundary() { if(boundary == null) boundary = "----" + UUID.randomUUID(); @@ -133,11 +155,10 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { public void addFrom(String name, String address) { if(this.from == null) this.from = new ArrayList<Rfc822Token>(1); - //this.from.add(formatAddress(name, address)); this.from.add(new Rfc822Token(name, address, null)); } public ArrayList<Rfc822Token> getSender() { - return from; + return sender; } public void setSender(ArrayList<Rfc822Token> sender) { this.sender = sender; @@ -145,7 +166,6 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { public void addSender(String name, String address) { if(this.sender == null) this.sender = new ArrayList<Rfc822Token>(1); - //this.sender.add(formatAddress(name, address)); this.sender.add(new Rfc822Token(name,address,null)); } public ArrayList<Rfc822Token> getTo() { @@ -157,7 +177,6 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { public void addTo(String name, String address) { if(this.to == null) this.to = new ArrayList<Rfc822Token>(1); - //this.to.add(formatAddress(name, address)); this.to.add(new Rfc822Token(name, address, null)); } public ArrayList<Rfc822Token> getCc() { @@ -169,7 +188,6 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { public void addCc(String name, String address) { if(this.cc == null) this.cc = new ArrayList<Rfc822Token>(1); - //this.cc.add(formatAddress(name, address)); this.cc.add(new Rfc822Token(name, address, null)); } public ArrayList<Rfc822Token> getBcc() { @@ -181,7 +199,6 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { public void addBcc(String name, String address) { if(this.bcc == null) this.bcc = new ArrayList<Rfc822Token>(1); - //this.bcc.add(formatAddress(name, address)); this.bcc.add(new Rfc822Token(name, address, null)); } public ArrayList<Rfc822Token> getReplyTo() { @@ -193,7 +210,6 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { public void addReplyTo(String name, String address) { if(this.replyTo == null) this.replyTo = new ArrayList<Rfc822Token>(1); - //this.replyTo.add(formatAddress(name, address)); this.replyTo.add(new Rfc822Token(name, address, null)); } public void setMessageId(String messageId) { @@ -208,49 +224,49 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { public String getContentType() { return contentType; } + public void setTextOnly(boolean textOnly) { + this.textOnly = textOnly; + } + public boolean getTextOnly() { + return textOnly; + } + public void setIncludeAttachemnts(boolean includeAttachments) { + this.includeAttachments = includeAttachments; + } + public boolean getIncludeAttachments() { + return includeAttachments; + } public void updateCharset() { charset = null; for(MimePart part : parts) { if(part.contentType != null && part.contentType.toUpperCase().contains("TEXT")) { charset = "UTF-8"; + break; } } } - /** - * Use this to format an address according to RFC 2822. - * @param name - * @param address - * @return - */ - public static String formatAddress(String name, String address) { - StringBuilder sb = new StringBuilder(); - Boolean nameSet = false; - if(name != null && !(name = name.trim()).equals("")) { - sb.append(name.trim()); - nameSet = true; - } - if(address != null && !(address = address.trim()).equals("")) - { - if(nameSet == true) - sb.append(":"); - sb.append("<").append(address).append(">"); + public int getSize() { + int message_size = 0; + for(MimePart part : parts) { + message_size += part.data.length; } - // TODO: Throw exception of the string is larger than 996 - return sb.toString(); + return message_size; } - /** * Encode an address header, and perform folding if needed. * @param sb The stringBuilder to write to * @param headerName The RFC 2822 header name - * @param addresses the reformatted address substrings to encode. Create - * these using {@link formatAddress} + * @param addresses the reformatted address substrings to encode. */ public void encodeHeaderAddresses(StringBuilder sb, String headerName, ArrayList<Rfc822Token> addresses) { - /* TODO: Do we need to encode the addresses if they contain illegal characters */ + /* TODO: Do we need to encode the addresses if they contain illegal characters? + * This depends of the outcome of errata 4176. The current spec. states to use UTF-8 + * where possible, but the RFCs states to use US-ASCII for the headers - hence encoding + * would be needed to support non US-ASCII characters. But the MAP spec states not to + * use any encoding... */ int partLength, lineLength = 0; lineLength += headerName.getBytes().length; sb.append(headerName); @@ -285,7 +301,7 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { * UTF-8 should be used for the entire <bmessage-body-content>. We let the MAP specification * take precedence above the RFC-2822. The code to */ -/* If we are to use US-ASCII anyway, here are the code for it. + /* If we are to use US-ASCII anyway, here are the code for it. if (subject != null){ // Use base64 encoding for the subject, as it may contain non US-ASCII characters or other // illegal (RFC822 header), and android do not seem to have encoders/decoders for quoted-printables @@ -313,11 +329,14 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { encodeHeaderAddresses(sb, "Bcc: ", bcc); // This includes folding if needed. if(replyTo != null) encodeHeaderAddresses(sb, "Reply-To: ", replyTo); // This includes folding if needed. - if(messageId != null) - sb.append("Message-Id: ").append(messageId).append("\r\n"); - if(contentType != null) - sb.append("Content-Type: ").append(contentType).append("; boundary=").append(getBoundary()); - sb.append("\r\n\r\n"); // If no headers exists, we still need two CRLF, hence keep it out of the if above. + if(includeAttachments == true) + { + if(messageId != null) + sb.append("Message-Id: ").append(messageId).append("\r\n"); + if(contentType != null) + sb.append("Content-Type: ").append(contentType).append("; boundary=").append(getBoundary()).append("\r\n"); + } + sb.append("\r\n"); // If no headers exists, we still need two CRLF, hence keep it out of the if above. } /* Notes on MMS @@ -338,12 +357,15 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { * that some of the headers should be excluded. * Additionally it is not clear how to handle attachments. There is a parameter in the * get message to include attachments, but since only 8-bit encoding is allowed, - * (hence neither base64 nor binary) there is not mechanism to embed the attachment in + * (hence neither base64 nor binary) there is no mechanism to embed the attachment in * the <bmessage-body-content>. * - * UPDATE: Errata xxx allows the needed encoding typed inside the <bmessage-body-content> + * UPDATE: Errata 4176 allows the needed encoding typed inside the <bmessage-body-content> * including Base64 and Quoted Printables - hence it is possible to encode non-us-ascii * messages - e.g. pictures and utf-8 strings with non-us-ascii content. + * It have not yet been adopted, but since the comments clearly suggest that it is allowed + * to use encoding schemes for non-text parts, it is still not clear what to do about non + * US-ASCII text in the headers. * */ /** @@ -361,9 +383,15 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { encoding = "8BIT"; // The encoding used encodeHeaders(sb); - for(MimePart part : parts) { - count++; - part.encode(sb, getBoundary(), (count == parts.size())); + if(getIncludeAttachments() == false) { + for(MimePart part : parts) { + part.encodePlainText(sb); /* We call encode on all parts, to include a tag, where an attachment is missing. */ + } + } else { + for(MimePart part : parts) { + count++; + part.encode(sb, getBoundary(), (count == parts.size())); + } } mmsBody = sb.toString(); @@ -372,26 +400,50 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { String tmpBody = mmsBody.replaceAll("END:MSG", "/END\\:MSG"); // Replace any occurrences of END:MSG with \END:MSG bodyFragments.add(tmpBody.getBytes("UTF-8")); } else { - bodyFragments.add(new byte[0]); // TODO: Is this allowed? (An empty message) + bodyFragments.add(new byte[0]); } return encodeGeneric(bodyFragments); } - private void parseMmsHeaders(String hdrPart) { + + /** + * Try to parse the hdrPart string as e-mail headers. + * @param hdrPart The string to parse. + * @return Null if the entire string were e-mail headers. The part of the string in which + * no headers were found. + */ + private String parseMmsHeaders(String hdrPart) { String[] headers = hdrPart.split("\r\n"); + String header; + hasHeaders = false; - for(String header : headers) { + for(int i = 0, c = headers.length; i < c; i++) { + header = headers[i]; + + /* We need to figure out if any headers are present, in cases where devices do not follow the e-mail RFCs. + * Skip empty lines, and then parse headers until a non-header line is found, at which point we treat the + * remaining as plain text. + */ if(header.trim() == "") continue; String[] headerParts = header.split(":",2); - if(headerParts.length != 2) - throw new IllegalArgumentException("Header not formatted correctly: " + header); + if(headerParts.length != 2) { + // We treat the remaining content as plain text. + StringBuilder remaining = new StringBuilder(); + for(; i < c; i++) + remaining.append(headers[i]); + + return remaining.toString(); + } + String headerType = headerParts[0].toUpperCase(); String headerValue = headerParts[1].trim(); // Address headers - // TODO: If this is empty, the MSE needs to fill it in + /* TODO: If this is empty, the MSE needs to fill it in before sending the message. + * This happens when sending the MMS, not sure what happens for e-mail. + */ if(headerType.contains("FROM")) { Rfc822Token tokens[] = Rfc822Tokenizer.tokenize(headerValue); from = new ArrayList<Rfc822Token>(Arrays.asList(tokens)); @@ -419,34 +471,40 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { messageId = headerValue; } else if(headerType.contains("DATE")) { - /* XXX: Set date */ + /* TODO: Do we need the date? */ } else if(headerType.contains("CONTENT-TYPE")) { String[] contentTypeParts = headerValue.split(";"); contentType = contentTypeParts[0]; // Extract the boundary if it exists - for(int i=1, n=contentTypeParts.length; i<n; i++) + for(int j=1, n=contentTypeParts.length; j<n; j++) { - if(contentTypeParts[i].contains("boundary")) { - boundary = contentTypeParts[i].split("boundary[\\s]*=", 2)[1].trim(); + if(contentTypeParts[j].contains("boundary")) { + boundary = contentTypeParts[j].split("boundary[\\s]*=", 2)[1].trim(); } } } + else if(headerType.contains("CONTENT-TRANSFER-ENCODING")) { + encoding = headerValue; + } else { if(D) Log.w(TAG,"Skipping unknown header: " + headerType + " (" + header + ")"); } } + return null; } private void parseMmsMimePart(String partStr) { - /**/ String[] parts = partStr.split("\r\n\r\n", 2); // Split the header from the body + String body; if(parts.length != 2) { - throw new IllegalArgumentException("Wrongly formatted email part - unable to locate header section"); + body = partStr; + } else { + body = parts[1]; } String[] headers = parts[0].split("\r\n"); MimePart newPart = addMimePart(); - String encoding = ""; + String partEncoding = encoding; /* Use the overall encoding as default */ for(String header : headers) { if(header.length() == 0) @@ -460,37 +518,53 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { String headerType = headerParts[0].toUpperCase(); String headerValue = headerParts[1].trim(); if(headerType.contains("CONTENT-TYPE")) { - // TODO: extract charset - as for + // TODO: extract charset? Only UTF-8 is allowed for TEXT typed parts newPart.contentType = headerValue; Log.d(TAG, "*** CONTENT-TYPE: " + newPart.contentType); } else if(headerType.contains("CONTENT-LOCATION")) { // This is used if the smil refers to a file name in its src= + newPart.contentLocation = headerValue; newPart.partName = headerValue; } else if(headerType.contains("CONTENT-TRANSFER-ENCODING")) { - encoding = headerValue; + partEncoding = headerValue; } else if(headerType.contains("CONTENT-ID")) { // This is used if the smil refers to a cid:<xxx> in it's src= newPart.contentId = headerValue; } + else if(headerType.contains("CONTENT-DISPOSITION")) { + // This is used if the smil refers to a cid:<xxx> in it's src= + newPart.contentDisposition = headerValue; + } else { if(D) Log.w(TAG,"Skipping unknown part-header: " + headerType + " (" + header + ")"); } } // Now for the body - if(encoding.toUpperCase().contains("BASE64")) { - newPart.data = Base64.decode(parts[1], Base64.DEFAULT); + newPart.data = decodeBody(body, partEncoding); + } + + private void parseMmsMimeBody(String body) { + MimePart newPart = addMimePart(); + newPart.data = decodeBody(body, encoding); + } + + private byte[] decodeBody(String body, String encoding) { + if(encoding != null && encoding.toUpperCase().contains("BASE64")) { + return Base64.decode(body, Base64.DEFAULT); } else { // TODO: handle other encoding types? - here we simply store the string data as bytes try { - newPart.data = parts[1].getBytes("UTF-8"); + return body.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { // This will never happen, as UTF-8 is mandatory on Android platforms } } + return null; } + private void parseMms(String message) { /* Overall strategy for decoding: * 1) split on first empty line to extract the header @@ -501,16 +575,43 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { * */ String[] messageParts; String[] mimeParts; + String remaining = null; + String messageBody = null; message = message.replaceAll("\\r\\n[ \\\t]+", ""); // Unfold messageParts = message.split("\r\n\r\n", 2); // Split the header from the body if(messageParts.length != 2) { - throw new IllegalArgumentException("Wrongly formatted email message - unable to locate header section"); + // Handle entire message as plain text + messageBody = message; + } + else + { + remaining = parseMmsHeaders(messageParts[0]); + // If we have some text not being a header, add it to the message body. + if(remaining != null) { + messageBody = remaining + messageParts[1]; + } + else { + messageBody = messageParts[1]; + } + } + + if(boundary == null) + { + // If the boundary is not set, handle as non-multi-part + parseMmsMimeBody(messageBody); + setTextOnly(true); + if(contentType == null) + contentType = "text/plain"; + parts.get(0).contentType = contentType; + } + else + { + mimeParts = messageBody.split("--" + boundary); + for(int i = 0; i < mimeParts.length - 1; i++) { + String part = mimeParts[i]; + if (part != null && (part.length() > 0)) + parseMmsMimePart(part); } - parseMmsHeaders(messageParts[0]); - mimeParts = messageParts[1].split("--" + boundary); - for(String part : mimeParts) { - if (part != null && (part.length() > 0)) - parseMmsMimePart(part); } } @@ -519,14 +620,13 @@ public class BluetoothMapbMessageMmsEmail extends BluetoothMapbMessage { * src="cid:1234@hest.net" refers to a part with Content-ID:<1234@hest.net>*/ @Override public void parseMsgPart(String msgPart) { - // TODO Auto-generated method stub parseMms(msgPart); } @Override public void parseMsgInit() { - // TODO Auto-generated method stub + // Not used for e-mail } diff --git a/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java b/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java index 07a2a6731..8107bd8fe 100644 --- a/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java +++ b/src/com/android/bluetooth/map/BluetoothMapbMessageSms.java @@ -17,6 +17,8 @@ package com.android.bluetooth.map; import java.io.UnsupportedEncodingException; import java.util.ArrayList; +import android.util.Log; + import com.android.bluetooth.map.BluetoothMapSmsPdu.SmsPdu; import com.android.bluetooth.map.BluetoothMapUtils.TYPE; @@ -45,7 +47,16 @@ public class BluetoothMapbMessageSms extends BluetoothMapbMessage { @Override public void parseMsgPart(String msgPart) { if(appParamCharset == BluetoothMapAppParams.CHARSET_NATIVE) { - smsBody += BluetoothMapSmsPdu.decodePdu(decodeBinary(msgPart), + if(D) Log.d(TAG, "Decoding \"" + msgPart + "\" as native PDU"); + byte[] msgBytes = decodeBinary(msgPart); + if(msgBytes.length > 0 && + msgBytes[0] < msgBytes.length-1 && + (msgBytes[msgBytes[0]+1] & 0x03) != 0x01) { + if(D) Log.d(TAG, "Only submit PDUs are supported"); + throw new IllegalArgumentException("Only submit PDUs are supported"); + } + + smsBody += BluetoothMapSmsPdu.decodePdu(msgBytes, type == TYPE.SMS_CDMA ? BluetoothMapSmsPdu.SMS_TYPE_CDMA : BluetoothMapSmsPdu.SMS_TYPE_GSM); } else { @@ -67,7 +78,7 @@ public class BluetoothMapbMessageSms extends BluetoothMapbMessage { if(smsBody != null) { String tmpBody = smsBody.replaceAll("END:MSG", "/END\\:MSG"); // Replace any occurrences of END:MSG with \END:MSG bodyFragments.add(tmpBody.getBytes("UTF-8")); - }else if (smsBodyPdus.size() > 0) { + }else if (smsBodyPdus != null && smsBodyPdus.size() > 0) { for (SmsPdu pdu : smsBodyPdus) { // This cannot(must not) contain END:MSG bodyFragments.add(encodeBinary(pdu.getData(),pdu.getScAddress()).getBytes("UTF-8")); diff --git a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java index c6283f1b5..c3811240e 100644 --- a/src/com/android/bluetooth/map/BluetoothMnsObexClient.java +++ b/src/com/android/bluetooth/map/BluetoothMnsObexClient.java @@ -37,9 +37,6 @@ import javax.obex.ObexTransport; import javax.obex.ResponseCodes; /** - * - * @author Casper Bonde, Henrik Ejersbo and Kim Schulz - * * The Message Notification Service class runs its own message handler thread, * to avoid executing long operations on the MAP service Thread. * This handler context is passed to the content observers, @@ -49,8 +46,8 @@ import javax.obex.ResponseCodes; public class BluetoothMnsObexClient extends Thread{ private static final String TAG = "BluetoothMnsObexClient"; - private static final boolean D = true; - private static final boolean V = true; + private static final boolean D = false; + private static final boolean V = false; public final static int MSG_SESSION_ERROR = 1; public final static int MSG_CONNECT_TIMEOUT = 2; @@ -69,7 +66,7 @@ public class BluetoothMnsObexClient extends Thread{ private Looper mLooper = null; // Used by the MAS to forward notification registrations public static final int MSG_MNS_NOTIFICATION_REGISTRATION = 1; - public static final int MSG_MNS_SHUTDOWN = 2; + public static final ParcelUuid BluetoothUuid_ObexMns = ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB"); @@ -109,10 +106,6 @@ public class BluetoothMnsObexClient extends Thread{ case MSG_MNS_NOTIFICATION_REGISTRATION: handleRegistration(msg.arg1 /*masId*/, msg.arg2 /*status*/); break; - case MSG_MNS_SHUTDOWN: - if(mObserverRegistered) - mObserver.unregisterObserver(); - break; default: break; } @@ -131,7 +124,6 @@ public class BluetoothMnsObexClient extends Thread{ mClientSession.disconnect(null); if (D) Log.d(TAG, "OBEX session disconnected"); } - mClientSession = null; } catch (IOException e) { Log.w(TAG, "OBEX session disconnect error " + e.getMessage()); } @@ -139,6 +131,7 @@ public class BluetoothMnsObexClient extends Thread{ if (mClientSession != null) { if (D) Log.d(TAG, "OBEX session close mClientSession"); mClientSession.close(); + mClientSession = null; if (D) Log.d(TAG, "OBEX session closed"); } } catch (IOException e) { @@ -148,6 +141,8 @@ public class BluetoothMnsObexClient extends Thread{ try { if (D) Log.d(TAG, "Close Obex Transport"); mTransport.close(); + mTransport = null; + mConnected = false; if (D) Log.d(TAG, "Obex Transport Closed"); } catch (IOException e) { Log.e(TAG, "mTransport.close error: " + e.getMessage()); @@ -166,8 +161,10 @@ public class BluetoothMnsObexClient extends Thread{ try { join(); } catch (InterruptedException e) { - if(V) Log.w(TAG, e); + if(V) Log.w(TAG, "got interrupted. Probably a connection shutdown"); } + mHandler = null; + mObserver = null; } private HeaderSet hsConnect = null; @@ -185,7 +182,6 @@ public class BluetoothMnsObexClient extends Thread{ if(mObserverRegistered == true) { mObserver.unregisterObserver(); mObserverRegistered = false; - disconnect(); } } else if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) { /* Connect if we do not have a connection, and start the content observers providing @@ -307,7 +303,7 @@ public class BluetoothMnsObexClient extends Thread{ outputStream.close(); } else { error = true; - // TBD - Is Output stream close needed here + outputStream.close(); putOperation.abort(); Log.i(TAG, "SendEvent interrupted"); } diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java index a45bc9a8c..55827d862 100755 --- a/src/com/android/bluetooth/pbap/BluetoothPbapService.java +++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java @@ -242,6 +242,8 @@ public class BluetoothPbapService extends Service { Intent timeoutIntent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); timeoutIntent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); + timeoutIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, + BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); sendBroadcast(timeoutIntent, BLUETOOTH_ADMIN_PERM); } // Release all resources @@ -250,7 +252,11 @@ public class BluetoothPbapService extends Service { removeTimeoutMsg = false; } } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { - if (!isWaitingAuthorization) { + int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, + BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); + + if ((!isWaitingAuthorization) || + (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS)) { // this reply is not for us return; } @@ -628,6 +634,8 @@ public class BluetoothPbapService extends Service { case USER_TIMEOUT: Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); + intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, + BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); sendBroadcast(intent); isWaitingAuthorization = false; stopObexServerSession(); diff --git a/tests/Android.mk b/tests/Android.mk new file mode 100755 index 000000000..a667d1dbe --- /dev/null +++ b/tests/Android.mk @@ -0,0 +1,19 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests +LOCAL_CERTIFICATE := platform + +LOCAL_JAVA_LIBRARIES := android.test.runner +LOCAL_STATIC_JAVA_LIBRARIES := com.android.emailcommon + +# Include all test java files. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := BluetoothProfileTests + +LOCAL_INSTRUMENTATION_FOR := Bluetooth + +include $(BUILD_PACKAGE) + diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml new file mode 100755 index 000000000..a9fc19c6f --- /dev/null +++ b/tests/AndroidManifest.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- package name must be unique so suffix with "tests" so package loader doesn't ignore us --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.bluetooth.tests"> + + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> + <uses-permission android:name="android.permission.ACCESS_BLUETOOTH_SHARE" /> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.BLUETOOTH" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <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.READ_CALL_LOG" /> + <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + <uses-permission android:name="com.android.permission.HANDOVER_STATUS" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + <uses-permission android:name="android.permission.NET_ADMIN" /> + <uses-permission android:name="android.permission.CALL_PRIVILEGED" /> + <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> + <uses-permission android:name="android.permission.NET_TUNNELING" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> + <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> + <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.BLUETOOTH_STACK" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/> + <uses-permission android:name="android.permission.MANAGE_USERS"/> + <uses-permission android:name="com.google.android.gallery3d.permission.GALLERY_PROVIDER"/> + <uses-permission android:name="com.android.gallery3d.permission.GALLERY_PROVIDER"/> + + <uses-permission android:name="android.permission.RECEIVE_SMS" /> + <uses-permission android:name="android.permission.READ_PHONE_STATE" /> + <uses-permission android:name="android.permission.SEND_SMS" /> + <uses-permission android:name="android.permission.READ_SMS" /> + <uses-permission android:name="android.permission.WRITE_SMS" /> + <uses-permission android:name="android.permission.READ_CONTACTS" /> + + <uses-permission android:name="com.android.email.permission.ACCESS_PROVIDER"/> + + <!-- For PBAP Owner Vcard Info --> + <uses-permission android:name="android.permission.READ_PROFILE"/> + + <!-- We add an application tag here just so that we can indicate that + this package needs to link against the android.test library, + which is needed when building test cases. --> + <application> + <uses-library android:name="android.test.runner" /> + <uses-permission android:name="android.permission.ACCESS_BLUETOOTH_SHARE" /> + <uses-permission android:permission="com.android.permission.WHITELIST_BLUETOOTH_DEVICE" /> + <path-permission + android:path="/btopp" + android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" /> + </application> + <!-- + This declares that this application uses the instrumentation test runner targeting + the package of com.android.bluetooth. To run the tests use the command: + "adb shell am instrument -w com.android.bluetooth.tests/android.test.InstrumentationTestRunner" + --> + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.bluetooth" + android:label="Tests for com.android.bluetooth"/> +</manifest> + diff --git a/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java b/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java new file mode 100644 index 000000000..45ab386e6 --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java @@ -0,0 +1,102 @@ +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.text.SimpleDateFormat; +import java.util.Date; + +import com.android.bluetooth.map.BluetoothMapContent; +import com.android.bluetooth.map.BluetoothMapContentObserver; + +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; + +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; + + 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.CLIENT_ID, + 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 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.CLIENT_ID + " : " + c.getInt(c.getColumnIndex(MessageColumns.CLIENT_ID)) + + "\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"); + if (c != null) { + Log.d(TAG, "c.getCount() = " + c.getCount()); + c.moveToPosition(-1); + while (c.moveToNext()) { + printEmail(c); + } + } else { + Log.d(TAG, "query failed"); + c.close(); + } + } + + public BluetoothMapContentTest() { + super(); + } + + public void testDumpMessages() { + mContext = this.getContext(); + mResolver = mContext.getContentResolver(); + dumpEmailMessageTable(); + } +} diff --git a/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java b/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java new file mode 100755 index 000000000..14c41da21 --- /dev/null +++ b/tests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java @@ -0,0 +1,505 @@ +package com.android.bluetooth.tests; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +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.BluetoothMapbMessage; +import com.android.bluetooth.map.BluetoothMapbMessageMmsEmail; +import com.android.bluetooth.map.BluetoothMapbMessageSms; +import org.apache.http.message.BasicHeaderValueFormatter; +import org.apache.http.message.BasicHeaderElement; + +/*** + * + * Test cases for the bMessage class. (encoding and decoding) + * + */ +public class BluetoothMapbMessageTest extends AndroidTestCase { + protected static String TAG = "BluetoothMapbMessageTest"; + protected static final boolean D = true; + + public BluetoothMapbMessageTest() { + super(); + } + + /*** + * Test encoding of a simple SMS text message (UTF8). This validates most parameters. + */ + public void testSmsEncodeText() { + BluetoothMapbMessageSms msg = new BluetoothMapbMessageSms(); + String str1 = + "BEGIN:BMSG\r\n" + + "VERSION:1.0\r\n" + + "STATUS:UNREAD\r\n" + + "TYPE:SMS_GSM\r\n" + + "FOLDER:telecom/msg/inbox\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" + + "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" + + "BEGIN:MSG\r\n" + + "This is a short message\r\n" + + "END:MSG\r\n" + + "END:BBODY\r\n" + + "END:BENV\r\n" + + "END:BMSG\r\n"; + + 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.setFolder("inbox"); + msg.setSmsBody("This is a short message"); + msg.setStatus(false); + msg.setType(TYPE.SMS_GSM); + try { + encoded = new String(msg.encode()); + if(D) Log.d(TAG, encoded); + assertTrue(str1.equals(encoded)); + } catch (UnsupportedEncodingException e) { + Log.d(TAG, "Encoding failed.",e); + assertTrue("Encoding failed.", true); + } + } + + /*** + * Test native Deliver PDU encoding (decoding not possible), based on the example in the MAP 1.1 specification. + * The difference between this PDU, and the one in the specification: + * - The invalid SC address 0191 is replaced with no address 00 + * - The "No more messages flag" is set (bit 2 in the second byte) + * - The phone number type is changed from private 91 to international 81 + * - The time is changed to local time, since the time zone cannot be controlled through the API + */ + public void testSmsEncodeNativeDeliverPdu() { + BluetoothMapbMessageSms msg = new BluetoothMapbMessageSms(); + SimpleDateFormat format = new SimpleDateFormat("yyMMddHHmmss"); + Date date = new Date(System.currentTimeMillis()); + String timeStr = format.format(date); // Format to YYMMDDTHHMMSS UTC time + ByteArrayOutputStream scTime = new ByteArrayOutputStream(7); + StringBuilder scTimeSb = new StringBuilder(); + byte[] timeChars; + try { + timeChars = timeStr.getBytes("US-ASCII"); + } catch (UnsupportedEncodingException e1) { + assertTrue("Failed to extract bytes from string using US-ASCII", true); + return; + } + + for(int i = 0, n = timeStr.length(); i < n; i+=2) { + scTime.write((timeChars[i+1]-0x30) << 4 | (timeChars[i]-0x30)); // Offset from ascii char to decimal value + } + + Calendar cal = Calendar.getInstance(); + int offset = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / (15 * 60 * 1000); /* offset in quarters of an hour */ + String offsetString; + if(offset < 0) { + offsetString = String.format("%1$02d", -(offset)); + char[] offsetChars = offsetString.toCharArray(); + scTime.write((offsetChars[1]-0x30) << 4 | 0x40 | (offsetChars[0]-0x30)); + } + else { + offsetString = String.format("%1$02d", offset); + char[] offsetChars = offsetString.toCharArray(); + scTime.write((offsetChars[1]-0x30) << 4 | (offsetChars[0]-0x30)); + } + byte[] scTimeData = scTime.toByteArray(); + for(int i = 0; i < scTimeData.length; i++) { + scTimeSb.append(Integer.toString((scTimeData[i] >> 4) & 0x0f,16)); // MS-nibble first + scTimeSb.append(Integer.toString( scTimeData[i] & 0x0f,16)); + } + if(D) Log.v(TAG, "Generated time string: " + scTimeSb.toString()); + String expected = + "BEGIN:BMSG\r\n" + + "VERSION:1.0\r\n" + + "STATUS:UNREAD\r\n" + + "TYPE:SMS_GSM\r\n" + + "FOLDER:telecom/msg/inbox\r\n" + + "BEGIN:VCARD\r\n" + + "VERSION:3.0\r\n" + + "FN:Casper Bonde\r\n" + + "N:Bonde,Casper\r\n" + + "TEL:00498912345678\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" + + "FN:Jens Hansen\r\n" + + "N:\r\n" + + "TEL:00498912345678\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" + + "ENCODING:G-7BIT\r\n" + + "LENGTH:94\r\n" + + "BEGIN:MSG\r\n" + + "00040E81009498214365870000" + scTimeSb.toString() + + "11CC32FD34079DDF20737A8E4EBBCF21\r\n" + + "END:MSG\r\n" + + "END:BBODY\r\n" + + "END:BENV\r\n" + + "END:BMSG\r\n"; + + 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.setFolder("inbox"); + /* TODO: extract current time, and build the expected string */ + msg.setSmsBodyPdus(BluetoothMapSmsPdu.getDeliverPdus("Let's go fishing!", "00498912345678", date.getTime())); + msg.setStatus(false); + msg.setType(TYPE.SMS_GSM); + try { + byte[] encodedBytes = msg.encode(); +// InputStream is = new ByteArrayInputStream(encodedBytes); + encoded = new String(encodedBytes); +// BluetoothMapbMessage newMsg = BluetoothMapbMessage.parse(is, BluetoothMapAppParams.CHARSET_NATIVE); +// String decoded = ((BluetoothMapbMessageSms) newMsg).getSmsBody(); + if(D) Log.d(TAG, "\nExpected: \n" + expected); + if(D) Log.d(TAG, "\nEncoded: \n" + encoded); +// if(D) Log.d(TAG, "\nDecoded: \n" + decoded); + assertTrue(expected.equalsIgnoreCase(encoded)); + } catch (UnsupportedEncodingException e) { + Log.d(TAG, "Encoding failed.",e); + assertTrue("Encoding failed.", true); + } + } + + /*** + * Test native Submit PDU encoding and decoding, based on the example in the MAP 1.1 specification. + * The difference between this PDU, and the one in the specification: + * - The invalid SC address 0191 is replaced with no address 00 + * - The PDU is converted to a submit PDU by adding the TP-MR and removing the service center time stamp. + * - The phone number type is changed from private 91 to international 81 + */ + public void testSmsEncodeDecodeNativeSubmitPdu() { + BluetoothMapbMessageSms msg = new BluetoothMapbMessageSms(); + String expected = + "BEGIN:BMSG\r\n" + + "VERSION:1.0\r\n" + + "STATUS:UNREAD\r\n" + + "TYPE:SMS_GSM\r\n" + + "FOLDER:telecom/msg/outbox\r\n" + + "BEGIN:VCARD\r\n" + + "VERSION:3.0\r\n" + + "FN:Casper Bonde\r\n" + + "N:Bonde,Casper\r\n" + + "TEL:00498912345678\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" + + "FN:Jens Hansen\r\n" + + "N:\r\n" + + "TEL:00498912345678\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" + + "ENCODING:G-7BIT\r\n" + + "LENGTH:82\r\n" + + "BEGIN:MSG\r\n" + /*Length 11 */ + "0001000E8100949821436587000011CC32FD34079DDF20737A8E4EBBCF21\r\n" + /* Length 62 */ + "END:MSG\r\n" + /* Length 9 */ + "END:BBODY\r\n" + + "END:BENV\r\n" + + "END:BMSG\r\n"; + + 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.setFolder("outbox"); + /* TODO: extract current time, and build the expected string */ + msg.setSmsBodyPdus(BluetoothMapSmsPdu.getSubmitPdus("Let's go fishing!", "00498912345678")); + msg.setStatus(false); + msg.setType(TYPE.SMS_GSM); + try { + byte[] encodedBytes = msg.encode(); + InputStream is = new ByteArrayInputStream(encodedBytes); + encoded = new String(encodedBytes); + BluetoothMapbMessage newMsg = BluetoothMapbMessage.parse(is, BluetoothMapAppParams.CHARSET_NATIVE); + String decoded = ((BluetoothMapbMessageSms) newMsg).getSmsBody(); + if(D) Log.d(TAG, "\nCalling encoder on decoded message to log its content"); + newMsg.encode(); + if(D) Log.d(TAG, "\nExpected: \n" + expected); + if(D) Log.d(TAG, "\nEncoded: \n" + encoded); + if(D) Log.d(TAG, "\nDecoded: \n" + decoded); + assertTrue("The encoded bMessage do not match the expected.", expected.equalsIgnoreCase(encoded)); + assertTrue("The decoded text is \"" + decoded + "\" - expected \"Let's go fishing!\"", decoded.equalsIgnoreCase("Let's go fishing!")); + } catch (UnsupportedEncodingException e) { + Log.d(TAG, "Encoding failed.",e); + assertTrue("Encoding failed.", true); + } + } + + /*** + * Test native Submit PDU encoding and decoding, based on the example in the MAP 1.1 specification. + * The difference between this PDU, and the one in the specification: + * - The invalid SC address 0191 is replaced with no address 00 + * - The PDU is converted to a submit PDU by adding the TP-MR and removing the service center time stamp. + * - The phone number type is changed from private 91 to international 81 + */ + public void testSmsEncodeDecodeNativeSubmitPduWithSc() { + BluetoothMapbMessageSms msg = new BluetoothMapbMessageSms(); + String encoded = + "BEGIN:BMSG\r\n" + + "VERSION:1.0\r\n" + + "STATUS:UNREAD\r\n" + + "TYPE:SMS_GSM\r\n" + + "FOLDER:telecom/msg/outbox\r\n" + + "BEGIN:VCARD\r\n" + + "VERSION:3.0\r\n" + + "FN:Casper Bonde\r\n" + + "N:Bonde,Casper\r\n" + + "TEL:00498912345678\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" + + "FN:Jens Hansen\r\n" + + "N:\r\n" + + "TEL:00498912345678\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" + + "ENCODING:G-7BIT\r\n" + + "LENGTH:58 \r\n" + + "BEGIN:MSG\r\n" + /*Length 11 */ + "018001000B912184254590F500000346F61B\r\n" + /* Length 38 */ + "END:MSG\r\n" + /* Length 9 */ + "END:BBODY\r\n" + + "END:BENV\r\n" + + "END:BMSG\r\n"; + try { + String expected = "Flo"; + InputStream is = new ByteArrayInputStream(encoded.getBytes("UTF-8")); + BluetoothMapbMessage newMsg = BluetoothMapbMessage.parse(is, BluetoothMapAppParams.CHARSET_NATIVE); + String decoded = ((BluetoothMapbMessageSms) newMsg).getSmsBody(); + if(D) Log.d(TAG, "\nEncoded: \n" + encoded); + if(D) Log.d(TAG, "\nDecoded: \n" + decoded); + assertTrue("Decoded string (" + decoded + ") did not match expected (" + expected + ")", expected.equals(decoded)); + } catch (UnsupportedEncodingException e) { + Log.d(TAG, "Encoding failed.",e); + assertTrue("Encoding failed.", false); + } + } + + /*** + * Validate that the folder is correctly truncated to 512 bytes, if a longer folder path + * is supplied. + */ + public void testFolderLengthTruncation() { + String folder = ""; + int levelCount = 0; + while(folder.length()<640) + folder += "/folder" + levelCount++; + + String expected = folder.substring(folder.length()-512, folder.length()); + + BluetoothMapbMessageSms msg = new BluetoothMapbMessageSms(); + msg.setFolder(folder); + msg.setStatus(false); + msg.setType(TYPE.SMS_GSM); + + try { + byte[] encoded = msg.encode(); + InputStream is = new ByteArrayInputStream(encoded); + if(D) Log.d(TAG, new String(encoded)); + BluetoothMapbMessage newMsg = BluetoothMapbMessage.parse(is, BluetoothMapAppParams.CHARSET_UTF8); + assertTrue("Wrong length expected 512, got " + expected.length(), expected.length() == 512); + Log.d(TAG, "expected: " + expected); + Log.d(TAG, "newMsg.getFolder(): " + newMsg.getFolder()); + assertTrue("Folder string did not match", expected.equals(newMsg.getFolder())); + + } catch (UnsupportedEncodingException e) { + Log.d(TAG, "Encoding failed.",e); + assertTrue("Encoding failed", false); + } + } + + /*** + * Test multipart message decoding. + */ + public void testSmsMultipartDecode() { + BluetoothMapbMessageSms msg = new BluetoothMapbMessageSms(); + String encoded = + "BEGIN:BMSG\r\n" + + "VERSION:1.0\r\n" + + "STATUS:READ\r\n" + + "TYPE:SMS_GSM\r\n" + + "FOLDER:/telecom/msg/outbox\r\n" + + "BEGIN:VCARD\r\n" + + "VERSION:2.1\r\n" + + "N:12485254094 \r\n" + + "TEL:12485254094\r\n" + + "END:VCARD\r\n" + + "BEGIN:BENV\r\n" + + "BEGIN:VCARD\r\n" + + "VERSION:2.1\r\n" + + "N:+12485254095 \r\n" + + "TEL:+12485254095\r\n" + + "END:VCARD\r\n" + + "BEGIN:BBODY\r\n" + + "ENCODING:G-7BIT\r\n" + + "LENGTH:762\r\n" + + "BEGIN:MSG\r\n" + + "018041000B912184254590F50000A0050003010301A8E8F41C949E83C220F69B0E7A9B41F7B79C3C07D1DF20F35BDE068541E3B77B1CA697DD617A990C6A97E7F3F0B90C0ABBC9203ABA0C32A7E5733A889E6E9741F437888E2E83E66537B92C07A5DBED32391DA697D97990FB4D4F9BF3A07619B476BFEFA03B3A4C07E5DF7550585E06B9DF74D0BC2E2F83F2EF3A681C7683C86F509A0EBABFEB\r\n" + + "END:MSG\r\n" + + "BEGIN:MSG\r\n" + + "018041000B912184254590F50000A0050003010302C820767A5D06D1D16550DA4D2FBBC96532485E1EA7E1E9B29B0E82B3CBE17919644EBBC9A0779D0EA2A3CB20735A3EA783E8E8B4FB0CA2BF41E437C8FDA683E6757919947FD741E3B01B447E83D274D0FD5D679341ECB7BD0C4AD341F7F01C44479741E6B47C4E0791C379D0DB0C6AE741F2F2BCDE2E83CC6F3928FFAECB41E57638CD06A5E7\r\n" + + "END:MSG\r\n" + + "BEGIN:MSG\r\n" + + "018041000B912184254590F500001A050003010303DC6F3A685E979741F9771D340EBB41E437\r\n" + + "END:MSG\r\n" + + "END:BBODY\r\n" + + "END:BENV\r\n" + + "END:BMSG\r\n"; + try { + InputStream is = new ByteArrayInputStream(encoded.getBytes("UTF-8")); + BluetoothMapbMessage newMsg = BluetoothMapbMessage.parse(is, BluetoothMapAppParams.CHARSET_NATIVE); + String decoded = ((BluetoothMapbMessageSms) newMsg).getSmsBody(); + if(D) Log.d(TAG, "\nEncoded: \n" + encoded); + if(D) Log.d(TAG, "\nDecoded: \n" + decoded); + } catch (UnsupportedEncodingException e) { + Log.d(TAG, "Decoding failed.",e); + assertTrue("Decoding failed.", false); + } + } + + /*** + * Test encoding of a simple MMS text message (UTF8). This validates most parameters. + */ + public void testMmsEncodeText() { + BluetoothMapbMessageMmsEmail msg = new BluetoothMapbMessageMmsEmail(); + String str1 = + "BEGIN:BMSG\r\n" + + "VERSION:1.0\r\n" + + "STATUS:UNREAD\r\n" + + "TYPE:MMS\r\n" + + "FOLDER:telecom/msg/inbox\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" + + "FN:Jørn 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" + + "BEGIN:MSG\r\n" + + "This is a short message\r\n" + + "END:MSG\r\n" + + "END:BBODY\r\n" + + "END:BENV\r\n" + + "END:BMSG\r\n"; + + 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.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"); + BluetoothMapbMessageMmsEmail.MimePart part = msg.addMimePart(); + part.partName = "partNameText"; + part.contentType ="dsfajfdlk/text/asdfafda"; + try { + part.data = new String("This is a short message\r\n").getBytes("UTF-8"); + } + catch (UnsupportedEncodingException e) { + if(D) Log.e(TAG, "UnsupportedEncodingException should never happen???", e); + assertTrue(false); + } + + part = msg.addMimePart(); + part.partName = "partNameimage"; + part.contentType = "dsfajfdlk/image/asdfafda"; + part.data = null; + + msg.setStatus(false); + msg.setType(TYPE.MMS); + try { + encoded = new String(msg.encode()); + if(D) Log.d(TAG, encoded); + assertTrue(str1.equals(encoded)); + } catch (UnsupportedEncodingException e) { + Log.d(TAG, "Encoding failed.",e); + assertTrue("Encoding failed.", true); + } + } + + public void testHeaderEncode() { + BasicHeaderElement header = new BasicHeaderElement("To","Jørgen <joergen@hest.com>"); + String headerStr = BasicHeaderValueFormatter.formatHeaderElement(header, true, BasicHeaderValueFormatter.DEFAULT); + if(D) Log.i(TAG, "The encoded header: " + headerStr); + } + +} + |