summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKim Schulz <k.schulz@samsung.com>2013-08-22 10:57:59 +0200
committerZhihai Xu <zhihaixu@google.com>2013-09-13 15:41:35 -0700
commit70be005a18a35ec5fcb46152f0dfbe82156efa3a (patch)
treecf122cc80b0a1f6c3b583507c163bf3f8aef2741
parentcd34ad74f093c4867e616ba247fe3853b06afebc (diff)
downloadandroid_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
-rw-r--r--AndroidManifest.xml22
-rw-r--r--res/values/strings_map.xml16
-rw-r--r--src/com/android/bluetooth/btservice/Config.java7
-rwxr-xr-xsrc/com/android/bluetooth/hfp/HeadsetService.java8
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapActivity.java284
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapAppParams.java102
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapAuthenticator.java2
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapContent.java281
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapContentObserver.java93
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapMessageListing.java23
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapMessageListingElement.java94
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapObexServer.java97
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapReceiver.java64
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapService.java759
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapSmsPdu.java187
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapbMessage.java148
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapbMessageMmsEmail.java270
-rw-r--r--src/com/android/bluetooth/map/BluetoothMapbMessageSms.java15
-rw-r--r--src/com/android/bluetooth/map/BluetoothMnsObexClient.java24
-rwxr-xr-xsrc/com/android/bluetooth/pbap/BluetoothPbapService.java10
-rwxr-xr-xtests/Android.mk19
-rwxr-xr-xtests/AndroidManifest.xml66
-rw-r--r--tests/src/com/android/bluetooth/tests/BluetoothMapContentTest.java102
-rwxr-xr-xtests/src/com/android/bluetooth/tests/BluetoothMapbMessageTest.java505
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);
+ }
+
+}
+