summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJackson Fan <xyfan@motorola.com>2009-07-19 12:25:14 +0800
committerJaikumar Ganesh <jaikumar@google.com>2009-07-23 11:53:12 -0700
commit2c282d5898ac0916470ebfa9ff26ba784cf4bb24 (patch)
tree6f459a34f97d82ef7754617be3b25eafdc37a969
parenteb37b3c2c9d59bc1c0f09f911f5bde077a63f4da (diff)
downloadandroid_packages_apps_Bluetooth-2c282d5898ac0916470ebfa9ff26ba784cf4bb24.tar.gz
android_packages_apps_Bluetooth-2c282d5898ac0916470ebfa9ff26ba784cf4bb24.tar.bz2
android_packages_apps_Bluetooth-2c282d5898ac0916470ebfa9ff26ba784cf4bb24.zip
Add pbap into bluetooth package
Update PBAP as patchset 5 Adjust some resouces strings after UI test Change localized resource usage Modify exception handle, rename a confusing variable name Remove compile dependency on 240 for now. Add TODO in code Some minor changes to address the comments Submit on-behalf of Yue Lixin <a5206c@motorola.com> Update PBAP as patchset 4 according to comments Format resource files Re-organize the logs Submit on-behalf of Jiafa Liu <pbx376@motorola.com>
-rw-r--r--AndroidManifest.xml18
-rw-r--r--res/layout/access.xml28
-rw-r--r--res/layout/auth.xml30
-rw-r--r--res/values/strings_pbap.xml28
-rw-r--r--src/com/android/bluetooth/pbap/BluetoothPbapActivity.java284
-rw-r--r--src/com/android/bluetooth/pbap/BluetoothPbapAuthenticator.java75
-rw-r--r--src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java884
-rw-r--r--src/com/android/bluetooth/pbap/BluetoothPbapReceiver.java111
-rw-r--r--src/com/android/bluetooth/pbap/BluetoothPbapRfcommTransport.java59
-rw-r--r--src/com/android/bluetooth/pbap/BluetoothPbapService.java634
-rw-r--r--src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java511
11 files changed, 2662 insertions, 0 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 491e02e26..9323cfc18 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -13,6 +13,9 @@
<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_PHONE_STATE" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
<application android:icon="@drawable/stat_sys_data_bt"
android:label="@string/app_name">
<uses-library android:name="javax.obex" />
@@ -75,5 +78,20 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
+ <activity android:name=".pbap.BluetoothPbapActivity"
+ android:label=" "
+ android:theme="@*android:style/Theme.Dialog.Alert">
+ <intent-filter>
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ <service android:name=".pbap.BluetoothPbapService" >
+ <action android:name="android.bluetooth.IBluetoothPbap"/>
+ </service>
+ <receiver android:name=".pbap.BluetoothPbapReceiver">
+ <intent-filter>
+ <action android:name="android.bluetooth.intent.action.BLUETOOTH_STATE_CHANGED"/>
+ </intent-filter>
+ </receiver>
</application>
</manifest>
diff --git a/res/layout/access.xml b/res/layout/access.xml
new file mode 100644
index 000000000..9fdb9587c
--- /dev/null
+++ b/res/layout/access.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="fill_parent"
+ android:layout_width="fill_parent">
+
+ <LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="fill_parent"
+ android:layout_width="fill_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <CheckBox android:id="@+id/alwaysallowed"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="@string/alwaysallowed" />
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/res/layout/auth.xml b/res/layout/auth.xml
new file mode 100644
index 000000000..d6a35cd0d
--- /dev/null
+++ b/res/layout/auth.xml
@@ -0,0 +1,30 @@
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="fill_parent"
+ android:layout_width="fill_parent">
+
+ <LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="fill_parent"
+ android:layout_width="fill_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <EditText
+ android:id="@+id/text"
+ android:layout_height="wrap_content"
+ android:layout_width="fill_parent"
+ android:layout_marginTop="20dip"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="20dip"
+ android:singleLine="true" />
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/res/values/strings_pbap.xml b/res/values/strings_pbap.xml
new file mode 100644
index 000000000..44f1fbdba
--- /dev/null
+++ b/res/values/strings_pbap.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="pbap_app_name">Bluetooth Pbap</string>
+ <string name="pbap_acceptance_dialog_title">%1$s would like to access your contacts and call history. Give access to %2$s?</string>
+ <string name="pbap_acceptance_dialog_header">File transfer</string>
+ <string name="pbap_session_key_dialog_title">Type session key for %1$s</string>
+ <string name="pbap_session_key_dialog_header">Bluetooth session key required</string>
+ <string name="pbap_acceptance_timeout_message">There was time out to accept connection with %1$s</string>
+ <string name="pbap_authentication_timeout_message">There was time out to input session key with %1$s</string>
+ <string name="bluetooth_transfer_header">Bluetooth transfer</string>
+ <string name="bluetooth_transfer_text">could not successfully establish phone book sharing with</string>
+ <string name="toast_connected">%1$s can now access your contacts and call history</string>
+ <string name="toast_disconnected">%1$s will not be given access</string>
+ <string name="pbap_notif_ticker">Phonebook access request</string>
+ <!-- Notification title when a Bluetooth device wants to pair with us -->
+ <string name="pbap_notif_title">PBAP request</string>
+ <!-- Notification message when a Bluetooth device wants to pair with us -->
+ <string name="pbap_notif_message">Allow phonebook access by %1$s</string>
+ <string name="auth_notif_ticker">Obex authentication request</string>
+ <!-- Notification title when a Bluetooth device wants to pair with us -->
+ <string name="auth_notif_title">Session Key</string>
+ <!-- Notification message when a Bluetooth device wants to pair with us -->
+ <string name="auth_notif_message">Type session key for %1$s</string>
+ <string name="alwaysallowed">Always allowed?</string>
+ <string name="defaultname">carkit</string>
+ <string name="unknownName">Unknown name</string>
+ <string name="defaultnumber">000000</string>
+</resources>
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java b/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
new file mode 100644
index 000000000..ab823f24a
--- /dev/null
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapActivity.java
@@ -0,0 +1,284 @@
+
+package com.android.bluetooth.pbap;
+
+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.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;
+
+/**
+ * PbapActivity shows two dialogues: One for accepting incoming pbap request and
+ * the other prompts the user to enter a session key for authentication with a
+ * remote Bluetooth device.
+ */
+public class BluetoothPbapActivity extends AlertActivity implements
+ DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener, TextWatcher {
+ private static final String TAG = "BluetoothPbapActivity";
+
+ private static final int BLUETOOTH_OBEX_AUTHKEY_MAX_LENGTH = 16;
+
+ private static final int DIALOG_YES_NO_CONNECT = 1;
+
+ private static final int DIALOG_YES_NO_AUTH = 2;
+
+ private View mView;
+
+ private EditText mKeyView;
+
+ private TextView messageView;
+
+ private String mSessionKey = "";
+
+ private int mCurrentDialog = 0;
+
+ private Button mOkButton;
+
+ private CheckBox mAlwaysAllowed;
+
+ private static final String USER_TIMEOUT = "user_timeout";
+
+ private boolean mUserConfirmTimeout = false;
+
+ private boolean mAlwaysAllowedValue = false;
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!BluetoothPbapService.USER_CONFIRM_TIMEOUT.equals(intent.getAction())) {
+ return;
+ }
+ onUserConfirmTimeout();
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent i = getIntent();
+ String action = i.getAction();
+ if (action.equals(BluetoothPbapService.ACCESS_REQUEST)) {
+ showPbapDialog(DIALOG_YES_NO_CONNECT);
+ mCurrentDialog = DIALOG_YES_NO_CONNECT;
+ } else if (action.equals(BluetoothPbapService.AUTH_CHALL)) {
+ showPbapDialog(DIALOG_YES_NO_AUTH);
+ mCurrentDialog = DIALOG_YES_NO_AUTH;
+ } else {
+ Log.e(TAG, "Error: this activity may be started only with intent "
+ + "PBAP_ACCESS_REQUEST or PBAP_AUTH_CHALL ");
+ finish();
+ }
+ registerReceiver(mReceiver, new IntentFilter(BluetoothPbapService.USER_CONFIRM_TIMEOUT));
+ }
+
+ private void showPbapDialog(int id) {
+ final AlertController.AlertParams p = mAlertParams;
+ switch (id) {
+ case DIALOG_YES_NO_CONNECT:
+ p.mIconId = android.R.drawable.ic_dialog_info;
+ p.mTitle = getString(R.string.pbap_acceptance_dialog_header);
+ p.mView = createView(DIALOG_YES_NO_CONNECT);
+ p.mPositiveButtonText = getString(android.R.string.yes);
+ p.mPositiveButtonListener = this;
+ p.mNegativeButtonText = getString(android.R.string.no);
+ p.mNegativeButtonListener = this;
+ mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
+ setupAlert();
+ break;
+ case DIALOG_YES_NO_AUTH:
+ p.mIconId = android.R.drawable.ic_dialog_info;
+ p.mTitle = getString(R.string.pbap_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 = BluetoothPbapService.getRemoteDeviceName();
+ switch (id) {
+ case DIALOG_YES_NO_CONNECT:
+ String mMessage1 = getString(R.string.pbap_acceptance_dialog_title, mRemoteName,
+ mRemoteName);
+ return mMessage1;
+ case DIALOG_YES_NO_AUTH:
+ String mMessage2 = getString(R.string.pbap_session_key_dialog_title, mRemoteName);
+ return mMessage2;
+ default:
+ return null;
+ }
+ }
+
+ private View createView(final int id) {
+ switch (id) {
+ case DIALOG_YES_NO_CONNECT:
+ mView = getLayoutInflater().inflate(R.layout.access, null);
+ messageView = (TextView)mView.findViewById(R.id.message);
+ messageView.setText(createDisplayText(id));
+ mAlwaysAllowed = (CheckBox)mView.findViewById(R.id.alwaysallowed);
+ mAlwaysAllowed.setOnCheckedChangeListener(new OnCheckedChangeListener() {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ mAlwaysAllowedValue = true;
+ }
+ }
+ });
+ return mView;
+ 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 (!mUserConfirmTimeout) {
+ if (mCurrentDialog == DIALOG_YES_NO_CONNECT) {
+ sendIntentToReceiver(BluetoothPbapService.ACCESS_ALLOWED,
+ BluetoothPbapService.ALWAYS_ALLOWED, mAlwaysAllowedValue);
+ } else if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
+ sendIntentToReceiver(BluetoothPbapService.AUTH_RESPONSE,
+ BluetoothPbapService.SESSION_KEY, mSessionKey);
+ mKeyView.removeTextChangedListener(this);
+ }
+ }
+ mUserConfirmTimeout = false;
+ finish();
+ }
+
+ private void onNegative() {
+ if (mCurrentDialog == DIALOG_YES_NO_CONNECT) {
+ sendIntentToReceiver(BluetoothPbapService.ACCESS_DISALLOWED, null, null);
+ } else if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
+ sendIntentToReceiver(BluetoothPbapService.AUTH_CANCELLED, 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(BluetoothPbapService.THIS_PACKAGE_NAME, BluetoothPbapReceiver.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(BluetoothPbapService.THIS_PACKAGE_NAME, BluetoothPbapReceiver.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;
+ }
+ }
+
+ private void onUserConfirmTimeout() {
+ mUserConfirmTimeout = true;
+ if (mCurrentDialog == DIALOG_YES_NO_CONNECT) {
+ messageView.setText(getString(R.string.pbap_acceptance_timeout_message,
+ BluetoothPbapService.getRemoteDeviceName()));
+ mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
+ mAlwaysAllowed.setVisibility(View.GONE);
+ mAlwaysAllowed.clearFocus();
+ } else if (mCurrentDialog == DIALOG_YES_NO_AUTH) {
+ messageView.setText(getString(R.string.pbap_authentication_timeout_message,
+ BluetoothPbapService.getRemoteDeviceName()));
+ mKeyView.setVisibility(View.GONE);
+ mKeyView.clearFocus();
+ mKeyView.removeTextChangedListener(this);
+ mOkButton.setEnabled(true);
+ mAlert.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ mUserConfirmTimeout = savedInstanceState.getBoolean(USER_TIMEOUT);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(USER_TIMEOUT, mUserConfirmTimeout);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ unregisterReceiver(mReceiver);
+ }
+
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ return true;
+ }
+
+ // Not used
+ public void beforeTextChanged(CharSequence s, int start, int before, int after) {
+ }
+
+ // Not used
+ 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);
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapAuthenticator.java b/src/com/android/bluetooth/pbap/BluetoothPbapAuthenticator.java
new file mode 100644
index 000000000..d8305dec1
--- /dev/null
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapAuthenticator.java
@@ -0,0 +1,75 @@
+
+package com.android.bluetooth.pbap;
+
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import javax.obex.Authenticator;
+import javax.obex.PasswordAuthentication;
+
+/**
+ * BluetoothPbapAuthenticator is a used by BluetoothObexServer for obex
+ * authentication procedure.
+ */
+public class BluetoothPbapAuthenticator implements Authenticator {
+ private static final String TAG = "BluetoothPbapAuthenticator";
+
+ private boolean mChallenged;
+
+ private boolean mAuthCancelled;
+
+ private String mSessionKey;
+
+ private Handler mCallback;
+
+ public BluetoothPbapAuthenticator(final Handler callback) {
+ mCallback = callback;
+ mChallenged = false;
+ mAuthCancelled = false;
+ mSessionKey = null;
+ }
+
+ public final void setChallenged(final boolean bool) {
+ mChallenged = bool;
+ }
+
+ public final synchronized void setCancelled(final boolean bool) {
+ mAuthCancelled = bool;
+ }
+
+ public final void setSessionKey(final String string) {
+ mSessionKey = string;
+ }
+
+ private void waitUserConfirmation() {
+ Message msg = Message.obtain(mCallback);
+ msg.what = BluetoothPbapService.MSG_OBEX_AUTH_CHALL;
+ msg.sendToTarget();
+ synchronized (this) {
+ while (!mChallenged && !mAuthCancelled) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Interrupted while waiting on isChalled");
+ }
+ }
+ }
+ }
+
+ public PasswordAuthentication onAuthenticationChallenge(final String description,
+ final boolean isUserIdRequired, final boolean isFullAccess) {
+ waitUserConfirmation();
+ if (mSessionKey.trim().length() != 0) {
+ PasswordAuthentication pa = new PasswordAuthentication(null, mSessionKey.getBytes());
+ return pa;
+ }
+ return null;
+ }
+
+ // TODO reserved for future use only.In case PSE challenge PCE
+ public byte[] onAuthenticationResponse(final byte[] userName) {
+ byte[] b = null;
+ return b;
+ }
+}
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
new file mode 100644
index 000000000..8d6d5dc05
--- /dev/null
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
@@ -0,0 +1,884 @@
+
+package com.android.bluetooth.pbap;
+
+import android.os.Message;
+import android.os.Handler;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+import javax.obex.ServerRequestHandler;
+import javax.obex.ResponseCodes;
+import javax.obex.ApplicationParameter;
+import javax.obex.Operation;
+import javax.obex.HeaderSet;
+
+public class BluetoothPbapObexServer extends ServerRequestHandler {
+
+ private static final String TAG = "BluetoothPbapObexServer";
+
+ private static final int UUID_LENGTH = 16;
+
+ private static final int LEAGAL_PATH_NUM = 10;
+
+ private static final int VCARD_NAME_MIN_LEN = 5;
+
+ private long mConnectionId;
+
+ private Handler mCallback = null;
+
+ // 128 bit UUID for PBAP
+ private static final byte[] PBAP_TARGET = new byte[] {
+ 0x79, 0x61, 0x35, (byte)0xf0, (byte)0xf0, (byte)0xc5, 0x11, (byte)0xd8, 0x09, 0x66,
+ 0x08, 0x00, 0x20, 0x0c, (byte)0x9a, 0x66
+ };
+
+ private static final String[] LEAGEL_PATH = {
+ "/telecom", "/telecom/pb", "/telecom/ich", "/telecom/och", "/telecom/mch",
+ "/telecom/cch", "/SIM1", "/SIM1/telecom", "/SIM1/telecom/ich", "/SIM1/telecom/och",
+ "/SIM1/telecom/mch", "/SIM1/telecom/cch", "/SIM1/telecom/pb"
+ };
+
+ // missed call history
+ private static final String MCH = "mch";
+
+ // incoming call history
+ private static final String ICH = "ich";
+
+ // outgoing call history
+ private static final String OCH = "och";
+
+ // combined call history
+ private static final String CCH = "cch";
+
+ // phone book
+ private static final String PB = "pb";
+
+ private static final String ICH_PATH = "/telecom/ich";
+
+ private static final String OCH_PATH = "/telecom/och";
+
+ private static final String MCH_PATH = "/telecom/mch";
+
+ private static final String CCH_PATH = "/telecom/cch";
+
+ private static final String PB_PATH = "/telecom/pb";
+
+ // type for list vcard objects
+ private static final String TYPE_LISTING = "x-bt/vcard-listing";
+
+ // type for get single vcard object
+ private static final String TYPE_VCARD = "x-bt/vcard";
+
+ // type for download all vcard objects
+ private static final String TYPE_PB = "x-bt/phonebook";
+
+ // record current path the client are browsing
+ private String mCurrentPath = "";
+
+ // record how many missed call happens since last client operation
+ private int mMissedCallSize = 0;
+
+ private static final int NEED_PB_SIZE = 0x01;
+
+ private static final int NEED_NEW_MISSED_CALL_NUMBER = 0x02;
+
+ public static final int NEED_PHONEBOOK = 0x04;
+
+ public static final int NEED_INCOMING_CALL_NUMBER = 0x08;
+
+ public static final int NEED_OUTGOING_CALL_NUMBER = 0x10;
+
+ public static final int NEED_MISSED_CALL_NUMBER = 0x20;
+
+ public static final int NEED_COMBINED_CALL_NUMBER = 0x40;
+
+ private static final int PRECONDITION_FAILED = -1;
+
+ private static final int INTERNAL_ERROR = -2;
+
+ public BluetoothPbapObexServer(Handler callback) {
+ super();
+ mConnectionId = -1;
+ mCallback = callback;
+ }
+
+ @Override
+ public int onConnect(final HeaderSet request, final HeaderSet reply) {
+ try {
+ byte[] uuid_tmp = (byte[])request.getHeader(HeaderSet.TARGET);
+ if (uuid_tmp.length != UUID_LENGTH) {
+ Log.w(TAG, "Wrong UUID length");
+ return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+ }
+ for (int i = 0; i < UUID_LENGTH; i++) {
+ if (uuid_tmp[i] != PBAP_TARGET[i]) {
+ Log.w(TAG, "Wrong UUID");
+ return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, e.toString());
+ return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ }
+
+ Message msg = Message.obtain(mCallback);
+ msg.what = BluetoothPbapService.MSG_SESSION_ESTABLISHED;
+ msg.sendToTarget();
+
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ @Override
+ public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
+ resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
+ if (mCallback != null) {
+ Message msg = Message.obtain(mCallback);
+ msg.what = BluetoothPbapService.MSG_SESSION_DISCONNECTED;
+ msg.sendToTarget();
+ }
+ }
+
+ @Override
+ public int onPut(final Operation op) {
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+
+ @Override
+ public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
+ final boolean create) {
+ String current_path_tmp = mCurrentPath;
+ String tmp_path = null;
+ try {
+ tmp_path = (String)request.getHeader(HeaderSet.NAME);
+ } catch (IOException e) {
+ Log.e(TAG, "Get name header fail");
+ return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ }
+ if (BluetoothPbapService.DBG) {
+ Log.d(TAG, "backup=" + backup + " create=" + create);
+ }
+ if (!backup && create) {
+ if (tmp_path == null) {
+ current_path_tmp = "";
+ } else {
+ current_path_tmp = current_path_tmp + "/" + tmp_path;
+ }
+ }
+
+ if (backup && create) {
+ if (current_path_tmp.length() != 0) {
+ current_path_tmp = current_path_tmp.substring(0, current_path_tmp.lastIndexOf("/"));
+ }
+ }
+
+ if ((current_path_tmp.length() != 0) && (!isLegalPath(current_path_tmp))) {
+ Log.w(TAG, "path is not legal");
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ mCurrentPath = current_path_tmp;
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ @Override
+ public void onClose() {
+ if (mCallback != null) {
+ Message msg = Message.obtain(mCallback);
+ msg.what = BluetoothPbapService.MSG_SERVERSESSION_CLOSE;
+ msg.sendToTarget();
+ }
+ }
+
+ @Override
+ public int onGet(final Operation op) {
+ HeaderSet request = null;
+ HeaderSet reply = new HeaderSet();
+ String type = "";
+ String name = "";
+ byte[] appParam = null;
+ AppParamValue appParamValue = new AppParamValue();
+ try {
+ request = op.getReceivedHeader();
+ type = (String)request.getHeader(HeaderSet.TYPE);
+ name = (String)request.getHeader(HeaderSet.NAME);
+ appParam = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
+ } catch (IOException e) {
+ Log.e(TAG, "request headers error");
+ return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ }
+ // Accroding to specification,the name header could be omitted such as
+ // sony erriccsonHBH-DS980
+ if (BluetoothPbapService.DBG) {
+ Log.d(TAG, "OnGet type is " + type + " name is " + name);
+ }
+ if (name == null) {
+ if (BluetoothPbapService.DBG) {
+ Log.i(TAG, "name =null,guess what carkit actually want from current path");
+ }
+ if (mCurrentPath.compareTo(PB_PATH) == 0) {
+ appParamValue.needTag |= NEED_PHONEBOOK;
+ }
+ if (mCurrentPath.compareTo(ICH_PATH) == 0) {
+ appParamValue.needTag |= NEED_INCOMING_CALL_NUMBER;
+ }
+ if (mCurrentPath.compareTo(OCH_PATH) == 0) {
+ appParamValue.needTag |= NEED_OUTGOING_CALL_NUMBER;
+ }
+ if (mCurrentPath.compareTo(CCH_PATH) == 0) {
+ appParamValue.needTag |= NEED_COMBINED_CALL_NUMBER;
+ }
+ if (mCurrentPath.compareTo(MCH_PATH) == 0) {
+ appParamValue.needTag |= NEED_MISSED_CALL_NUMBER;
+ }
+ } else {// we have weak name checking here to provide better
+ // compatibility with other devices,although unique name such as
+ // "pb.vcf" is required by SIG spec.
+ if (name.contains(PB.subSequence(0, PB.length()))) {
+ appParamValue.needTag |= NEED_PHONEBOOK;
+ if (BluetoothPbapService.DBG) {
+ Log.v(TAG, "download phonebook request");
+ }
+ }
+ if (name.contains(ICH.subSequence(0, ICH.length()))) {
+ appParamValue.needTag |= NEED_INCOMING_CALL_NUMBER;
+ if (BluetoothPbapService.DBG) {
+ Log.v(TAG, "download incoming calls request");
+ }
+ }
+ if (name.contains(OCH.subSequence(0, OCH.length()))) {
+ appParamValue.needTag |= NEED_OUTGOING_CALL_NUMBER;
+ if (BluetoothPbapService.DBG) {
+ Log.v(TAG, "download outgoing calls request");
+ }
+ }
+ if (name.contains(CCH.subSequence(0, CCH.length()))) {
+ appParamValue.needTag |= NEED_COMBINED_CALL_NUMBER;
+ if (BluetoothPbapService.DBG) {
+ Log.v(TAG, "download combined calls request");
+ }
+ }
+ if (name.contains(MCH.subSequence(0, MCH.length()))) {
+ appParamValue.needTag |= NEED_MISSED_CALL_NUMBER;
+ if (BluetoothPbapService.DBG) {
+ Log.v(TAG, "download missed calls request");
+ }
+ }
+ }
+ // listing request
+ if (type.equals(TYPE_LISTING)) {
+ return pullVcardListing(appParam, appParamValue, reply, op);
+ }
+ // pull vcard entry request
+ else if (type.equals(TYPE_VCARD)) {
+ return pullVcardEntry(appParam, appParamValue, op, name, mCurrentPath);
+ }
+ // down load phone book request
+ else if (type.equals(TYPE_PB)) {
+ return pullPhonebook(appParam, appParamValue, reply, op, name);
+ } else {
+ Log.w(TAG, "unknown type request!!!");
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ } // end of onGet()
+
+ /** check whether path is legal */
+ private final boolean isLegalPath(final String str) {
+ if (str.length() == 0) {
+ return true;
+ }
+ for (int i = 0; i < LEAGAL_PATH_NUM; i++) {
+ if (str.equals(LEAGEL_PATH[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private class AppParamValue {
+ public int maxListCount;
+
+ public int listStartOffset;
+
+ public String searchValue;
+
+ public String searchAttr;
+
+ public int needTag;
+
+ public boolean vcard21;
+
+ public AppParamValue() {
+ maxListCount = 0;
+ listStartOffset = 0;
+ searchValue = "";
+ searchAttr = "";
+ needTag = 0x00;
+ vcard21 = true;
+ }
+
+ public void dump() {
+ Log.i(TAG, "maxListCount=" + maxListCount + " listStartOffset=" + listStartOffset
+ + " searchValue=" + searchValue + " searchAttr=" + searchAttr + " needTag="
+ + needTag + " vcard21=" + vcard21);
+ }
+ }
+
+ /** To parse obex application parameter */
+ private final boolean parseApplicationParameter(final byte[] appParam,
+ AppParamValue appParamValue) {
+ int i = 0;
+ boolean badRequest = true;
+ while (i < appParam.length) {
+ switch (appParam[i]) {
+ case ApplicationParameter.TRIPLET_TAGID.FILTER_TAGID:
+ i += 2; // length and tag field in triplet
+ i += ApplicationParameter.TRIPLET_LENGTH.FILTER_LENGTH;
+ break;
+ case ApplicationParameter.TRIPLET_TAGID.ORDER_TAGID:
+ i += 2; // length and tag field in triplet
+ i += ApplicationParameter.TRIPLET_LENGTH.ORDER_LENGTH;
+ break;
+ case ApplicationParameter.TRIPLET_TAGID.SEARCH_VALUE_TAGID:
+ i += 1; // length field in triplet
+ for (int k = 1; k <= appParam[i]; k++) {
+ appParamValue.searchValue += Byte.toString(appParam[i + k]);
+ }
+ // length of search value is variable
+ i += appParam[i];
+ i += 1;
+ break;
+ case ApplicationParameter.TRIPLET_TAGID.SEARCH_ATTRIBUTE_TAGID:
+ i += 2;
+ appParamValue.searchAttr = Byte.toString(appParam[i]);
+ i += ApplicationParameter.TRIPLET_LENGTH.SEARCH_ATTRIBUTE_LENGTH;
+ break;
+ case ApplicationParameter.TRIPLET_TAGID.MAXLISTCOUNT_TAGID:
+ i += 2;
+ if (appParam[i] == 0 && appParam[i + 1] == 0) {
+ appParamValue.needTag |= NEED_PB_SIZE;
+ } else {
+ int highValue = appParam[i] & 0xff;
+ int lowValue = appParam[i + 1] & 0xff;
+ appParamValue.maxListCount = highValue * 256 + lowValue;
+ }
+ i += ApplicationParameter.TRIPLET_LENGTH.MAXLISTCOUNT_LENGTH;
+ break;
+ case ApplicationParameter.TRIPLET_TAGID.LISTSTARTOFFSET_TAGID:
+ i += 2;
+ if (appParam[i] == 0 && appParam[i + 1] == 0) {
+ appParamValue.listStartOffset = 0;
+ } else {
+ int highValue = appParam[i] & 0xff;
+ int lowValue = appParam[i + 1] & 0xff;
+ appParamValue.listStartOffset = highValue * 256 + lowValue;
+ }
+ i += ApplicationParameter.TRIPLET_LENGTH.LISTSTARTOFFSET_LENGTH;
+ break;
+ case ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID:
+ i += 2;// length field in triplet
+ i += ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH;
+ appParamValue.needTag |= NEED_PB_SIZE;
+ break;
+ case ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID:
+ i += 1;
+ appParamValue.needTag |= NEED_NEW_MISSED_CALL_NUMBER;
+ i += ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH;
+ break;
+ case ApplicationParameter.TRIPLET_TAGID.FORMAT_TAGID:
+ i += 2;// length field in triplet
+ if (Byte.toString(appParam[i]).compareTo("0") != 0) {
+ appParamValue.vcard21 = false;
+ }
+ i += ApplicationParameter.TRIPLET_LENGTH.FORMAT_LENGTH;
+ break;
+ default:
+ badRequest = false;
+ break;
+ }
+ }
+ return badRequest;
+ }
+
+ /** Form and Send an XML format String to client for Phone book listing */
+ private final int createVcardListingXml(final int type, final Operation op,
+ final int maxListCount, final int listStartOffset, final String searchValue,
+ String searchAttr) {
+ OutputStream out = null;
+ StringBuilder result = new StringBuilder();
+ int itemsFound = 0;
+ result.append("<?xml version=\"1.0\"?>");
+ result.append("<!DOCTYPE vcard-listing SYSTEM \"vcard-listing.dtd\">");
+ result.append("<vCard-listing version=\"1.0\">");
+
+ // Phonebook listing request
+ if (type == NEED_PHONEBOOK) {
+ // if searchAttr is not set by client,make searchAttr by name
+ // as default;
+ if (searchAttr == null || searchAttr.trim().length() == 0) {
+ if (BluetoothPbapService.DBG) {
+ Log.i(TAG, "searchAttr is not set, assume search by name");
+ }
+ searchAttr = "0";
+ }
+ // begin of search by name
+ if (searchAttr.compareTo("0") == 0) {
+ ArrayList<String> nameList = BluetoothPbapService.getPhonebookNameList();
+ int size = nameList.size() >= maxListCount ? maxListCount : nameList.size();
+ if (BluetoothPbapService.DBG) {
+ Log.d(TAG, "search by name, size=" + size + " offset=" + listStartOffset
+ + " searchValue=" + searchValue);
+ }
+ // if searchValue if not set by client,provide the entire
+ // list by name
+ if (searchValue == null || searchValue.trim().length() == 0) {
+ for (int j = listStartOffset, i = 0; i < size; i++, j++) {
+ result.append("<card handle=\"" + j + ".vcf\" name=\"" + nameList.get(j)
+ + "\"" + "/>");
+ itemsFound++;
+ }
+ } else {
+ for (int j = listStartOffset, i = 0; i < size; i++, j++) {
+ // only find the name which begins with the searchValue
+ if (nameList.get(j).indexOf(searchValue) == 0) {
+ itemsFound++;
+ result.append("<card handle=\"" + j + ".vcf\" name=\""
+ + nameList.get(j) + "\"" + "/>");
+ }
+ }
+ }
+ }// end of search by name
+ // begin of search by number
+ else if (searchAttr.compareTo("1") == 0) {
+ ArrayList<String> numberList = BluetoothPbapService.getPhonebookNumberList();
+ int size = numberList.size() >= maxListCount ? maxListCount : numberList.size();
+ if (BluetoothPbapService.DBG) {
+ Log.d(TAG, "search by number, size=" + size + " offset=" + listStartOffset
+ + " searchValue=" + searchValue);
+ }
+ // if searchValue if not set by client,provide the entire
+ // list by number
+ if (searchValue == null || searchValue.trim().length() == 0) {
+ for (int j = listStartOffset, i = 0; i < size; i++, j++) {
+ result.append("<card handle=\"" + j + ".vcf\" number=\""
+ + numberList.get(j) + "\"" + "/>");
+ itemsFound++;
+ }
+ } else {
+ for (int j = listStartOffset, i = 0; i < size; i++, j++) {
+ // only find the name which begins with the searchValue
+ if (numberList.get(j).indexOf(searchValue.trim()) == 0) {
+ itemsFound++;
+ result.append("<card handle=\"" + j + ".vcf\" number=\""
+ + numberList.get(j) + "\"" + "/>");
+ }
+ }
+ }
+ }// end of search by number
+ else {
+ return PRECONDITION_FAILED;
+ }
+ }
+ // Call history listing request
+ else {
+ ArrayList<String> nameList = BluetoothPbapService.getCallLogList(type);
+ int size = nameList.size() >= maxListCount ? maxListCount : nameList.size();
+ if (BluetoothPbapService.DBG) {
+ Log.d(TAG, "call log list, size=" + size + " offset=" + listStartOffset);
+ }
+ // listing object begin with 1.vcf
+ for (int j = listStartOffset + 1, i = 0; i < size; i++, j++) {
+ result.append("<card handle=\"" + j + ".vcf\" name=\"" + nameList.get(j - 1) + "\""
+ + "/>");
+ itemsFound++;
+ }
+ }
+ result.append("</vCard-listing>");
+ try {
+ out = op.openOutputStream();
+ out.write(result.toString().getBytes());
+ out.flush();
+ } catch (IOException e) {
+ Log.e(TAG, "output stream fail " + e.toString());
+ itemsFound = INTERNAL_ERROR;
+ } finally {
+ if (!closeStream(out, op)) {
+ itemsFound = INTERNAL_ERROR;
+ }
+ }
+
+ if (BluetoothPbapService.DBG) {
+ Log.d(TAG, "itemsFound =" + itemsFound);
+ }
+ return itemsFound;
+ }
+
+ /**
+ * Function to send obex header back to client such as get phonebook size
+ * request
+ */
+ private final int pushHeader(final Operation op, final HeaderSet reply) {
+ OutputStream outputStream = null;
+ int ret = ResponseCodes.OBEX_HTTP_OK;
+ try {
+ op.sendHeaders(reply);
+ outputStream = op.openOutputStream();
+ outputStream.flush();
+ } catch (IOException e) {
+ Log.e(TAG, e.toString());
+ ret = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ } finally {
+ if (!closeStream(outputStream, op)) {
+ ret = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ }
+ }
+ return ret;
+ }
+
+ /** Function to send vcard data to client */
+ private final int pushBytes(final Operation op, final String string) {
+ if (string == null) {
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+ byte[] filebytes;
+ int ret = ResponseCodes.OBEX_HTTP_OK;
+ OutputStream outputStream = null;
+ if (BluetoothPbapService.DBG) {
+ Log.d(TAG, "Send Data");
+ Log.d(TAG, string);
+ }
+ try {
+ outputStream = op.openOutputStream();
+ filebytes = string.getBytes();
+ outputStream.write(filebytes);
+ } catch (IOException e) {
+ Log.e(TAG, "open outputstrem failed" + e.toString());
+ ret = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ } finally {
+ if (!closeStream(outputStream, op)) {
+ ret = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
+ }
+ }
+ return ret;
+ }
+
+ private final int pullVcardListing(byte[] appParam, AppParamValue appParamValue,
+ HeaderSet reply, final Operation op) {
+ String searchAttr = appParamValue.searchAttr.trim();
+ if (!parseApplicationParameter(appParam, appParamValue)) {
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ if (BluetoothPbapService.DBG) {
+ appParamValue.dump();
+ }
+ if (searchAttr == null || searchAttr.length() == 0) {
+ return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+ }
+ if (searchAttr.compareTo("0") != 0 && searchAttr.compareTo("1") != 0) {
+ Log.w(TAG, "search attr not supported");
+ if (searchAttr.compareTo("2") == 0) {
+ // search by sound is not supported currently
+ Log.w(TAG, "do not support search by sound");
+ return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
+ }
+ return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
+ }
+
+ ApplicationParameter ap = new ApplicationParameter();
+ if ((appParamValue.needTag & NEED_PB_SIZE) == NEED_PB_SIZE) {
+ int size = 0;
+ byte[] pbsize = new byte[2];
+ if ((appParamValue.needTag & NEED_PHONEBOOK) == NEED_PHONEBOOK) {
+ size = BluetoothPbapService.getPhonebookSize(NEED_PHONEBOOK);
+ } else if ((appParamValue.needTag & NEED_INCOMING_CALL_NUMBER) == NEED_INCOMING_CALL_NUMBER) {
+ size = BluetoothPbapService.getPhonebookSize(NEED_INCOMING_CALL_NUMBER);
+ } else if ((appParamValue.needTag & NEED_OUTGOING_CALL_NUMBER) == NEED_OUTGOING_CALL_NUMBER) {
+ size = BluetoothPbapService.getPhonebookSize(NEED_OUTGOING_CALL_NUMBER);
+ } else if ((appParamValue.needTag & NEED_COMBINED_CALL_NUMBER) == NEED_COMBINED_CALL_NUMBER) {
+ size = BluetoothPbapService.getPhonebookSize(NEED_COMBINED_CALL_NUMBER);
+ } else if ((appParamValue.needTag & NEED_MISSED_CALL_NUMBER) == NEED_MISSED_CALL_NUMBER) {
+ size = BluetoothPbapService.getPhonebookSize(NEED_MISSED_CALL_NUMBER);
+ mMissedCallSize = size;
+ }
+ pbsize[0] = (byte)((size / 256) & 0xff);// HIGH VALUE
+ pbsize[1] = (byte)((size % 256) & 0xff);// LOW VALUE
+ ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID,
+ ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize);
+ reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
+ return pushHeader(op, reply);
+ }
+
+ if ((appParamValue.needTag & NEED_NEW_MISSED_CALL_NUMBER) == NEED_NEW_MISSED_CALL_NUMBER) {
+ byte[] misnum = new byte[1];
+ int nmnum = BluetoothPbapService.getPhonebookSize(NEED_MISSED_CALL_NUMBER)
+ - mMissedCallSize;
+ nmnum = nmnum > 0 ? nmnum : 0;
+ misnum[0] = (byte)nmnum;
+ ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
+ ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
+ reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
+ return pushHeader(op, reply);
+ }
+
+ else if ((appParamValue.needTag & NEED_PB_SIZE) == 0
+ && ((appParamValue.needTag & NEED_PHONEBOOK) == NEED_PHONEBOOK)) {
+ if (createVcardListingXml(NEED_PHONEBOOK, op, appParamValue.maxListCount,
+ appParamValue.listStartOffset, appParamValue.searchValue,
+ appParamValue.searchAttr) <= 0) {
+ return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+ }
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ else if ((appParamValue.needTag & NEED_PB_SIZE) == 0
+ && ((appParamValue.needTag & NEED_INCOMING_CALL_NUMBER) == NEED_INCOMING_CALL_NUMBER)) {
+ if (createVcardListingXml(NEED_INCOMING_CALL_NUMBER, op, appParamValue.maxListCount,
+ appParamValue.listStartOffset, null, null) <= 0) {
+ return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+ }
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ else if ((appParamValue.needTag & NEED_PB_SIZE) == 0
+ && ((appParamValue.needTag & NEED_OUTGOING_CALL_NUMBER) == NEED_OUTGOING_CALL_NUMBER)) {
+ if (createVcardListingXml(NEED_OUTGOING_CALL_NUMBER, op, appParamValue.maxListCount,
+ appParamValue.listStartOffset, null, null) <= 0) {
+ return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+ }
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ else if ((appParamValue.needTag & NEED_PB_SIZE) == 0
+ && ((appParamValue.needTag & NEED_COMBINED_CALL_NUMBER) == NEED_COMBINED_CALL_NUMBER)) {
+ if (createVcardListingXml(NEED_COMBINED_CALL_NUMBER, op, appParamValue.maxListCount,
+ appParamValue.listStartOffset, null, null) <= 0) {
+ return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+ }
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ else if ((appParamValue.needTag & NEED_PB_SIZE) == 0
+ && ((appParamValue.needTag & NEED_MISSED_CALL_NUMBER) == NEED_MISSED_CALL_NUMBER)) {
+ if (createVcardListingXml(NEED_MISSED_CALL_NUMBER, op, appParamValue.maxListCount,
+ appParamValue.listStartOffset, null, null) <= 0) {
+ return ResponseCodes.OBEX_HTTP_NOT_FOUND;
+ }
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+ return ResponseCodes.OBEX_HTTP_OK;
+ }
+
+ private final int pullVcardEntry(byte[] appParam, AppParamValue appParamValue,
+ final Operation op, final String name, final String current_path) {
+ boolean vcard21 = true;
+ if (!parseApplicationParameter(appParam, appParamValue))
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ String str = "";
+ String strIndex = "";
+ int intIndex = 0;
+ if (name == null || name.length() < VCARD_NAME_MIN_LEN) {
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ strIndex = name.substring(0, name.length() - VCARD_NAME_MIN_LEN + 1);
+ if (strIndex.trim().length() != 0) {
+ try {
+ intIndex = Integer.parseInt(strIndex);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "catch number format exception " + e.toString());
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ }
+ if (current_path.compareTo(PB_PATH) == 0) {
+ if (intIndex < 0 || intIndex >= BluetoothPbapService.getPhonebookSize(NEED_PHONEBOOK))
+ return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+ if (intIndex >= 0)
+ str = BluetoothPbapService.getPhonebook(NEED_PHONEBOOK, intIndex, vcard21);
+ } else if (current_path.compareTo(ICH_PATH) == 0) {
+ if (intIndex <= 0
+ || intIndex > BluetoothPbapService.getPhonebookSize(NEED_INCOMING_CALL_NUMBER))
+ return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+ if (intIndex >= 1)
+ str = BluetoothPbapService.getPhonebook(NEED_INCOMING_CALL_NUMBER, intIndex - 1,
+ vcard21);
+ } else if (current_path.compareTo(OCH_PATH) == 0) {
+ if (intIndex <= 0
+ || intIndex > BluetoothPbapService.getPhonebookSize(NEED_OUTGOING_CALL_NUMBER))
+ return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+ if (intIndex >= 1)
+ str = BluetoothPbapService.getPhonebook(NEED_OUTGOING_CALL_NUMBER, intIndex - 1,
+ vcard21);
+ } else if (current_path.compareTo(CCH_PATH) == 0) {
+ if (intIndex == 0)
+ return ResponseCodes.OBEX_HTTP_OK;
+ if (intIndex < 0
+ || intIndex > BluetoothPbapService.getPhonebookSize(NEED_COMBINED_CALL_NUMBER))
+ return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+ if (intIndex >= 1)
+ str = BluetoothPbapService.getPhonebook(NEED_COMBINED_CALL_NUMBER, intIndex - 1,
+ vcard21);
+ } else if (current_path.compareTo(MCH_PATH) == 0) {
+ if (intIndex <= 0
+ || intIndex > BluetoothPbapService.getPhonebookSize(NEED_MISSED_CALL_NUMBER))
+ return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+ if (intIndex >= 1)
+ str = BluetoothPbapService.getPhonebook(NEED_MISSED_CALL_NUMBER, intIndex - 1,
+ vcard21);
+ } else {
+ Log.w(TAG, "wrong path!");
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ return pushBytes(op, str);
+ }
+
+ private final int pullPhonebook(byte[] appParam, AppParamValue appParamValue, HeaderSet reply,
+ final Operation op, final String name) {
+ boolean vcard21 = true;
+ String str;
+ String strstr = "";
+ int pbSize = 0;
+ int size = 0;
+ int pos = 0;
+ // code start for passing PTS3.2 TC_PSE_PBD_BI_01_C
+ if (name != null) {
+ int dotIndex = name.indexOf(".");
+ String vcf = "vcf";
+ if (dotIndex >= 0 && dotIndex <= name.length()) {
+ if (name.regionMatches(dotIndex + 1, vcf, 0, vcf.length()) == false) {
+ Log.w(TAG, "name is not .vcf");
+ return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
+ }
+ }
+ }
+ // code end for passing PTS3.2 TC_PSE_PBD_BI_01_C
+ if (!parseApplicationParameter(appParam, appParamValue)) {
+ return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
+ }
+ // check whether to send application response parameter to client
+ ApplicationParameter ap = new ApplicationParameter();
+ if ((appParamValue.needTag & NEED_PB_SIZE) == NEED_PB_SIZE) {
+ byte[] pbsize = new byte[2];
+ if ((appParamValue.needTag & NEED_PHONEBOOK) == NEED_PHONEBOOK) {
+ size = BluetoothPbapService.getPhonebookSize(NEED_PHONEBOOK);
+ } else if ((appParamValue.needTag & NEED_INCOMING_CALL_NUMBER) == NEED_INCOMING_CALL_NUMBER) {
+ size = BluetoothPbapService.getPhonebookSize(NEED_INCOMING_CALL_NUMBER);
+ } else if ((appParamValue.needTag & NEED_OUTGOING_CALL_NUMBER) == NEED_OUTGOING_CALL_NUMBER) {
+ size = BluetoothPbapService.getPhonebookSize(NEED_OUTGOING_CALL_NUMBER);
+ } else if ((appParamValue.needTag & NEED_COMBINED_CALL_NUMBER) == NEED_COMBINED_CALL_NUMBER) {
+ size = BluetoothPbapService.getPhonebookSize(NEED_COMBINED_CALL_NUMBER);
+ } else if ((appParamValue.needTag & NEED_MISSED_CALL_NUMBER) == NEED_MISSED_CALL_NUMBER) {
+ size = BluetoothPbapService.getPhonebookSize(NEED_MISSED_CALL_NUMBER);
+ mMissedCallSize = size;
+ }
+ pbsize[0] = (byte)((size / 256) & 0xff);// HIGH VALUE
+ pbsize[1] = (byte)((size % 256) & 0xff);// LOW VALUE
+ ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.PHONEBOOKSIZE_TAGID,
+ ApplicationParameter.TRIPLET_LENGTH.PHONEBOOKSIZE_LENGTH, pbsize);
+ reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
+ return pushHeader(op, reply);
+ }
+
+ if ((appParamValue.needTag & NEED_NEW_MISSED_CALL_NUMBER) == NEED_NEW_MISSED_CALL_NUMBER) {
+ byte[] misnum = new byte[1];
+ int nmnum = BluetoothPbapService.getPhonebookSize(NEED_MISSED_CALL_NUMBER)
+ - mMissedCallSize;
+ nmnum = nmnum > 0 ? nmnum : 0;
+ misnum[0] = (byte)nmnum;
+ ap.addAPPHeader(ApplicationParameter.TRIPLET_TAGID.NEWMISSEDCALLS_TAGID,
+ ApplicationParameter.TRIPLET_LENGTH.NEWMISSEDCALLS_LENGTH, misnum);
+ reply.setHeader(HeaderSet.APPLICATION_PARAMETER, ap.getAPPparam());
+ return pushHeader(op, reply);
+ }
+
+ if ((appParamValue.needTag & NEED_PHONEBOOK) == NEED_PHONEBOOK) {
+ pbSize = BluetoothPbapService.getPhonebookSize(NEED_PHONEBOOK);
+ size = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize;
+ for (pos = appParamValue.listStartOffset; pos < size; pos++) {
+ str = BluetoothPbapService.getPhonebook(NEED_PHONEBOOK, pos, vcard21);
+ strstr += str;
+ }
+ }
+
+ if ((appParamValue.needTag & NEED_INCOMING_CALL_NUMBER) == NEED_INCOMING_CALL_NUMBER) {
+ pbSize = BluetoothPbapService.getPhonebookSize(NEED_INCOMING_CALL_NUMBER);
+ size = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize;
+ for (pos = appParamValue.listStartOffset; pos < size; pos++) {
+ str = BluetoothPbapService.getPhonebook(NEED_INCOMING_CALL_NUMBER, pos, vcard21);
+ strstr += str;
+ }
+ }
+
+ if ((appParamValue.needTag & NEED_OUTGOING_CALL_NUMBER) == NEED_OUTGOING_CALL_NUMBER) {
+ pbSize = BluetoothPbapService.getPhonebookSize(NEED_OUTGOING_CALL_NUMBER);
+ size = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize;
+ for (pos = appParamValue.listStartOffset; pos < size; pos++) {
+ str = BluetoothPbapService.getPhonebook(NEED_OUTGOING_CALL_NUMBER, pos, vcard21);
+ strstr += str;
+ }
+ }
+
+ if ((appParamValue.needTag & NEED_COMBINED_CALL_NUMBER) == NEED_COMBINED_CALL_NUMBER) {
+ pbSize = BluetoothPbapService.getPhonebookSize(NEED_COMBINED_CALL_NUMBER);
+ size = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize;
+ for (pos = appParamValue.listStartOffset; pos < size; pos++) {
+ str = BluetoothPbapService.getPhonebook(NEED_COMBINED_CALL_NUMBER, pos, vcard21);
+ strstr += str;
+ }
+ }
+
+ if ((appParamValue.needTag & NEED_MISSED_CALL_NUMBER) == NEED_MISSED_CALL_NUMBER) {
+ pbSize = BluetoothPbapService.getPhonebookSize(NEED_MISSED_CALL_NUMBER);
+ size = pbSize >= appParamValue.maxListCount ? appParamValue.maxListCount : pbSize;
+ for (pos = appParamValue.listStartOffset; pos < size; pos++) {
+ str = BluetoothPbapService.getPhonebook(NEED_MISSED_CALL_NUMBER, pos, vcard21);
+ strstr += str;
+ }
+ }
+ return pushBytes(op, strstr);
+ }
+
+ private boolean closeStream(final OutputStream out, final Operation op) {
+ boolean returnvalue = true;
+ try {
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "outputStream close failed" + e.toString());
+ returnvalue = false;
+ }
+ try {
+ if (op != null) {
+ op.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "oeration close failed" + e.toString());
+ returnvalue = false;
+ }
+ return returnvalue;
+ }
+
+ public final void setConnectionID(final long id) {
+ if ((id < -1) || (id > 0xFFFFFFFFL)) {
+ throw new IllegalArgumentException("Illegal Connection ID");
+ }
+ mConnectionId = id;
+ }
+
+ /**
+ * Retrieves the connection ID that is being used in the present connection.
+ * This method will return -1 if no connection ID is being used.
+ *
+ * @return the connection id being used or -1 if no connection ID is being
+ * used
+ */
+ public final long getConnectionID() {
+ return mConnectionId;
+ }
+
+ // Reserved for future use.In case PSE challenge PCE and PCE input wrong
+ // session key.
+ public final void onAuthenticationFailure(final byte[] userName) {
+ }
+}
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapReceiver.java b/src/com/android/bluetooth/pbap/BluetoothPbapReceiver.java
new file mode 100644
index 000000000..d693c155d
--- /dev/null
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapReceiver.java
@@ -0,0 +1,111 @@
+
+package com.android.bluetooth.pbap;
+
+import com.android.bluetooth.R;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothError;
+import android.bluetooth.BluetoothIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.util.Log;
+
+public class BluetoothPbapReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "BluetoothPbapReceiver";
+
+ public static final int NOTIFICATION_ID_ACCESS = 1;
+
+ public static final int NOTIFICATION_ID_AUTH = 2;
+
+ static final Object mStartingServiceSync = new Object();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Intent i = new Intent();
+ i.putExtras(intent);
+ i.setClass(context, BluetoothPbapService.class);
+ String action = intent.getAction();
+ i.putExtra("action", action);
+ if (action.equals(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION)
+ || action.equals(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION)) {
+ int state = intent.getIntExtra(BluetoothIntent.BLUETOOTH_STATE, BluetoothError.ERROR);
+ i.putExtra(BluetoothIntent.BLUETOOTH_STATE, state);
+ if ((state != BluetoothDevice.BLUETOOTH_STATE_TURNING_ON)
+ && (state != BluetoothDevice.BLUETOOTH_STATE_TURNING_OFF)) {
+ beginStartingService(context, i);
+ }
+ } else {
+ beginStartingService(context, i);
+ }
+ }
+
+ public static void makeNewPbapNotification(Context mContext, String action) {
+
+ NotificationManager nm = (NotificationManager)mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ Notification notification = null;
+ Resources res = mContext.getResources();
+ String name = BluetoothPbapService.getRemoteDeviceName();
+ // Create an intent triggered by clicking on the status icon.
+ Intent clickIntent = new Intent();
+ clickIntent.setClass(mContext, BluetoothPbapActivity.class);
+ clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ clickIntent.setAction(action);
+
+ // Create an intent triggered by clicking on the
+ // "Clear All Notifications" button
+ Intent deleteIntent = new Intent();
+ deleteIntent.setClass(mContext, BluetoothPbapReceiver.class);
+
+ if (action.equals(BluetoothPbapService.ACCESS_REQUEST)) {
+ deleteIntent.setAction(BluetoothPbapService.ACCESS_DISALLOWED);
+ notification = new Notification(android.R.drawable.stat_sys_data_bluetooth, res
+ .getString(R.string.pbap_notif_ticker), System.currentTimeMillis());
+ notification.setLatestEventInfo(mContext, res.getString(R.string.pbap_notif_title), res
+ .getString(R.string.pbap_notif_message, name), PendingIntent
+ .getActivity(mContext, 0, clickIntent, 0));
+
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+ notification.deleteIntent = PendingIntent.getBroadcast(mContext, 0, deleteIntent, 0);
+ nm.notify(NOTIFICATION_ID_ACCESS, notification);
+ } else if (action.equals(BluetoothPbapService.AUTH_CHALL)) {
+ deleteIntent.setAction(BluetoothPbapService.AUTH_CANCELLED);
+ notification = new Notification(android.R.drawable.stat_sys_data_bluetooth, res
+ .getString(R.string.auth_notif_ticker), System.currentTimeMillis());
+ notification.setLatestEventInfo(mContext, res.getString(R.string.auth_notif_title), res
+ .getString(R.string.auth_notif_message, name), PendingIntent
+ .getActivity(mContext, 0, clickIntent, 0));
+
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+ notification.deleteIntent = PendingIntent.getBroadcast(mContext, 0, deleteIntent, 0);
+ nm.notify(NOTIFICATION_ID_AUTH, notification);
+ }
+ }
+
+ public static void removePbapNotification(Context mContext, int id) {
+ NotificationManager nm = (NotificationManager)mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.cancel(id);
+ }
+
+ private static void beginStartingService(Context context, Intent intent) {
+ synchronized (mStartingServiceSync) {
+ context.startService(intent);
+ }
+ }
+
+ public static void finishStartingService(Service service, int startId) {
+ synchronized (mStartingServiceSync) {
+ if (service.stopSelfResult(startId)) {
+ Log.i(TAG, "successfully stopped pbap service");
+ }
+ }
+ }
+}
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapRfcommTransport.java b/src/com/android/bluetooth/pbap/BluetoothPbapRfcommTransport.java
new file mode 100644
index 000000000..242f4b1c8
--- /dev/null
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapRfcommTransport.java
@@ -0,0 +1,59 @@
+
+package com.android.bluetooth.pbap;
+
+import android.bluetooth.BluetoothSocket;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.obex.ObexTransport;
+
+public class BluetoothPbapRfcommTransport implements ObexTransport {
+ private BluetoothSocket mSocket = null;
+
+ public BluetoothPbapRfcommTransport(BluetoothSocket rfs) {
+ super();
+ this.mSocket = rfs;
+ }
+
+ public void close() throws IOException {
+ mSocket.close();
+ }
+
+ public DataInputStream openDataInputStream() throws IOException {
+ return new DataInputStream(openInputStream());
+ }
+
+ public DataOutputStream openDataOutputStream() throws IOException {
+ return new DataOutputStream(openOutputStream());
+ }
+
+ public InputStream openInputStream() throws IOException {
+ return mSocket.getInputStream();
+ }
+
+ public OutputStream openOutputStream() throws IOException {
+ return mSocket.getOutputStream();
+ }
+
+ public void connect() throws IOException {
+ }
+
+ public void create() throws IOException {
+ }
+
+ public void disconnect() throws IOException {
+ }
+
+ public void listen() throws IOException {
+ }
+
+ public boolean isConnected() throws IOException {
+ return true;
+ // TODO implement
+ }
+
+}
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
new file mode 100644
index 000000000..3ad61bbb9
--- /dev/null
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -0,0 +1,634 @@
+
+package com.android.bluetooth.pbap;
+
+import com.android.bluetooth.R;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothError;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothIntent;
+// TODO: have dependency on framework/base
+//import android.bluetooth.BluetoothPbap;
+import android.bluetooth.BluetoothSocket;
+import android.bluetooth.BluetoothServerSocket;
+// TODO: have dependency on framework/base
+//import android.bluetooth.IBluetoothPbap;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import javax.obex.ServerSession;
+
+public class BluetoothPbapService extends Service {
+
+ private static final String TAG = "BluetoothPbapService";
+
+ public static final boolean DBG = true;
+
+ /**
+ * Intent indicating incoming connection request which is sent to
+ * BluetoothPbapActivity
+ */
+ public static final String ACCESS_REQUEST = "com.android.bluetooth.pbap.accessrequest";
+
+ /**
+ * Intent indicating incoming connection request accepted by user which is
+ * sent from BluetoothPbapActivity
+ */
+ public static final String ACCESS_ALLOWED = "com.android.bluetooth.pbap.accessallowed";
+
+ /**
+ * Intent indicating incoming connection request denied by user which is
+ * sent from BluetoothPbapActivity
+ */
+ public static final String ACCESS_DISALLOWED = "com.android.bluetooth.pbap.accessdisallowed";
+
+ /**
+ * Intent indicating incoming obex authentication request which is from
+ * PCE(Carkit)
+ */
+ public static final String AUTH_CHALL = "com.android.bluetooth.pbap.authchall";
+
+ /**
+ * Intent indicating obex session key input complete by user which is sent
+ * from BluetoothPbapActivity
+ */
+ public static final String AUTH_RESPONSE = "com.android.bluetooth.pbap.authresponse";
+
+ /**
+ * Intent indicating user canceled obex authentication session key input
+ * which is sent from BluetoothPbapActivity
+ */
+ public static final String AUTH_CANCELLED = "com.android.bluetooth.pbap.authcancelled";
+
+ /**
+ * Intent indicating user confirmation timeout which is sent to
+ * BluetoothPbapActivity
+ */
+ public static final String USER_CONFIRM_TIMEOUT = "com.android.bluetooth.pbap.userconfirmtimeout";
+
+ /**
+ * Intent Extra name indicating always allowed which is sent from
+ * BluetoothPbapActivity
+ */
+ public static final String ALWAYS_ALLOWED = "com.android.bluetooth.pbap.alwaysallowed";
+
+ /**
+ * Intent Extra name indicating session key which is sent from
+ * BluetoothPbapActivity
+ */
+ public static final String SESSION_KEY = "com.android.bluetooth.pbap.sessionkey";
+
+ public static final String THIS_PACKAGE_NAME = "com.android.bluetooth";
+
+ public static final int MSG_SERVERSESSION_CLOSE = 5000;
+
+ public static final int MSG_SESSION_ESTABLISHED = 5001;
+
+ public static final int MSG_SESSION_DISCONNECTED = 5002;
+
+ public static final int MSG_OBEX_AUTH_CHALL = 5003;
+
+ private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+
+ private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
+
+ private static final int START_LISTENER = 1;
+
+ private static final int USER_TIMEOUT = 2;
+
+ private static final int AUTH_TIMEOUT = 3;
+
+ private PowerManager.WakeLock mWakeLock = null;
+
+ private BluetoothDevice mBluetooth;
+
+ private SocketAcceptThread mAcceptThread = null;
+
+ private BluetoothPbapObexServer mPbapServer;
+
+ private ServerSession mServerSession = null;
+
+ private BluetoothServerSocket mServerSocket = null;
+
+ private BluetoothSocket mConnSocket = null;
+
+ private String mDeviceAddr = null;
+
+ private static String sLocalPhoneNum = null;
+
+ private static String sLocalPhoneName = null;
+
+ private static String sRemoteDeviceName = null;
+
+ private String mRemoteAddr = null;
+
+ private boolean mHasStarted = false;
+
+ private int mState;
+
+ private int mStartId = -1;
+
+ private static final int PORT_NUM = 19;
+
+ private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
+
+ private static final int SOCKET_ACCEPT_TIMEOUT_VALUE = 5000;
+
+ private static final int TIME_TO_WAIT_VALUE = 6000;
+
+ private static BluetoothPbapVcardManager sVcardManager;
+
+ private CharSequence mTmpTxt;
+
+ private BluetoothPbapAuthenticator mAuth = null;
+
+ public BluetoothPbapService() {
+ // TODO: have dependency on framework/base
+ // mState = BluetoothPbap.STATE_DISCONNECTED;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mBluetooth = (BluetoothDevice)getSystemService(Context.BLUETOOTH_SERVICE);
+ if (mBluetooth != null) {
+ mDeviceAddr = mBluetooth.getAddress();
+ }
+ sVcardManager = new BluetoothPbapVcardManager(BluetoothPbapService.this);
+ if (!mHasStarted && mDeviceAddr != null) {
+ mHasStarted = true;
+ Log.i(TAG, "Starting PBAP service");
+ int state = mBluetooth.getBluetoothState();
+ if (state == BluetoothDevice.BLUETOOTH_STATE_ON) {
+ mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
+ .obtainMessage(START_LISTENER), TIME_TO_WAIT_VALUE);
+ }
+ }
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ mStartId = startId;
+ if (mBluetooth == null || mDeviceAddr == null) {
+ Log.w(TAG, "Stopping BluetoothHeadsetService: "
+ + "device does not have BT or device is not ready");
+ closeService(); // release all resources
+ } else {
+ parseIntent(intent);
+ }
+ }
+
+ // process the intent from receiver
+ private void parseIntent(final Intent intent) {
+ String action = intent.getExtras().getString("action");
+ int state = intent.getIntExtra(BluetoothIntent.BLUETOOTH_STATE, BluetoothError.ERROR);
+ if (action.equals(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION)) {
+ if (state == BluetoothDevice.BLUETOOTH_STATE_OFF) {
+ closeService(); // release all resources
+ }
+ }
+
+ if (action.equals(ACCESS_ALLOWED)) {
+ mSessionStatusHandler.removeMessages(USER_TIMEOUT);
+ if (intent.getBooleanExtra(ALWAYS_ALLOWED, false)) {
+ // TODO: have dependency on device trust feature implementation
+ // mBluetooth.setTrust(mRemoteAddr, true);
+ }
+ try {
+ // In case carkit time out and try to use HFP for phonebook
+ // access ,while UI still there for user to comfirm
+ if (mConnSocket != null) {
+ startObexServerSession();
+ } else {
+ obexServerSessionClose();
+ }
+ } catch (IOException ex) {
+ Log.e(TAG, "Caught the error: " + ex.toString());
+ }
+ }
+
+ if (action.equals(ACCESS_DISALLOWED)) {
+ mSessionStatusHandler.removeMessages(USER_TIMEOUT);
+ obexServerSessionClose();
+ }
+
+ if (action.equals(AUTH_RESPONSE)) {
+ mSessionStatusHandler.removeMessages(USER_TIMEOUT);
+ String sessionkey = intent.getStringExtra(SESSION_KEY);
+ notifyAuthKeyInput(sessionkey);
+ }
+
+ if (action.equals(AUTH_CANCELLED)) {
+ mSessionStatusHandler.removeMessages(USER_TIMEOUT);
+ notifyAuthCancelled();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ // TODO: have dependency on framework/base
+ // setState(BluetoothPbap.STATE_DISCONNECTED,
+ // BluetoothPbap.RESULT_CANCELED);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // TODO: have dependency on framework/base
+ // return mBinder;
+ return null;
+ }
+
+ public static int getPhonebookSize(final int type) {
+ if (DBG) {
+ Log.d(TAG, "getPhonebookSzie type=" + type);
+ }
+ switch (type) {
+ case BluetoothPbapObexServer.NEED_PHONEBOOK:
+ return sVcardManager.getPhonebookSize();
+ default:
+ return sVcardManager.getCallHistorySize(type);
+ }
+ }
+
+ public static String getPhonebook(final int type, final int pos, final boolean vcardType) {
+ if (DBG) {
+ Log.d(TAG, "getPhonebook type=" + type + " pos=" + pos + " vcardType=" + vcardType);
+ }
+ switch (type) {
+ case BluetoothPbapObexServer.NEED_PHONEBOOK:
+ return sVcardManager.getPhonebook(pos, vcardType);
+ default:
+ return sVcardManager.getCallHistory(pos, type, vcardType);
+ }
+ }
+
+ public static String getLocalPhoneNum() {
+ return sLocalPhoneNum;
+ }
+
+ public static String getLocalPhoneName() {
+ return sLocalPhoneName;
+ }
+
+ public static String getRemoteDeviceName() {
+ return sRemoteDeviceName;
+ }
+
+ // Used for phone book listing by name
+ public static ArrayList<String> getPhonebookNameList() {
+ return sVcardManager.loadNameList();
+ }
+
+ // Used for phone book listing by number
+ public static ArrayList<String> getPhonebookNumberList() {
+ return sVcardManager.loadNumberList();
+ }
+
+ // Used for call history listing
+ public static ArrayList<String> getCallLogList(final int type) {
+ return sVcardManager.loadCallHistoryList(type);
+ }
+
+ private void notifyAuthKeyInput(final String key) {
+ synchronized (mAuth) {
+ mAuth.setSessionKey(key);
+ mAuth.setChallenged(true);
+ mAuth.notify();
+ }
+ }
+
+ private void notifyAuthCancelled() {
+ synchronized (mAuth) {
+ mAuth.setCancelled(true);
+ mAuth.notify();
+ }
+ }
+
+ private final boolean initSocket() {
+ try {
+ // It is mandatory for PSE to support initiation of bonding and
+ // encryption.
+ mServerSocket = BluetoothServerSocket.listenUsingRfcommOn(PORT_NUM);
+ } catch (IOException ex) {
+ Log.e(TAG, "initSocket " + ex.toString());
+ return false;
+ }
+ return true;
+ }
+
+ private final void startObexServerSession() throws IOException {
+ // acquire the wakeLock before start Obex transaction thread
+ if (mWakeLock == null) {
+ PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "StartingObexPbapTransaction");
+ mWakeLock.setReferenceCounted(false);
+ mWakeLock.acquire();
+ }
+ TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
+ if (tm != null) {
+ sLocalPhoneNum = tm.getLine1Number();
+ sLocalPhoneName = tm.getLine1AlphaTag();
+ }
+ mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler);
+ mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler);
+ synchronized (mAuth) {
+ mAuth.setChallenged(false);
+ mAuth.setCancelled(false);
+ }
+ BluetoothPbapRfcommTransport transport = new BluetoothPbapRfcommTransport(mConnSocket);
+ mServerSession = new ServerSession(transport, mPbapServer, mAuth);
+ // TODO: have dependency on framework/base
+ // setState(BluetoothPbap.STATE_CONNECTED);
+ }
+
+ private final void closeSocket(boolean server, boolean accept) throws IOException {
+ if (server == true) {
+ if (mServerSocket != null) {
+ mServerSocket.close();
+ }
+ mServerSocket = null;
+ }
+
+ if (accept == true) {
+ if (mConnSocket != null) {
+ mConnSocket.close();
+ }
+ mConnSocket = null;
+ }
+ }
+
+ private final void closeService() {
+ 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;
+ }
+ try {
+ closeSocket(true, true);
+ } catch (IOException ex) {
+ Log.e(TAG, "Caught the error: " + ex);
+ }
+ mHasStarted = false;
+ BluetoothPbapReceiver.finishStartingService(BluetoothPbapService.this, mStartId);
+ }
+
+ private void obexServerSessionClose() {
+ // Release the wake lock if obex transaction is over
+ if (mWakeLock != null) {
+ mWakeLock.release();
+ mWakeLock = null;
+ }
+ mServerSession = null;
+ mAcceptThread = null;
+ try {
+ closeSocket(false, true);
+ } catch (IOException e) {
+ Log.e(TAG, "Caught the error: " + e.toString());
+ }
+ // Last obex transaction is finished,we start to listen for incoming
+ // connection again
+ if (mBluetooth.isEnabled()) {
+ startRfcommSocketListener();
+ }
+ // TODO: have dependency on framework/base
+ // setState(BluetoothPbap.STATE_DISCONNECTED);
+ }
+
+ /**
+ * A thread that runs in the background waiting for remote rfcomm
+ * connect.Once a remote socket connected, this thread shall be
+ * shutdown.When the remote disconnect,this thread shall run again waiting
+ * for next request.
+ */
+ private class SocketAcceptThread extends Thread {
+
+ private boolean stopped = false;
+
+ private boolean trust;
+
+ @Override
+ public void run() {
+ while (!stopped) {
+ try {
+ mConnSocket = mServerSocket.accept(SOCKET_ACCEPT_TIMEOUT_VALUE);
+ } catch (IOException ex) {
+ if (stopped) {
+ break;
+ }
+ if (DBG) {
+ Log.v(TAG, "Caught the error in socketAcceptThread: " + ex);
+ }
+ }
+ if (mConnSocket != null) {
+ mRemoteAddr = mConnSocket.getAddress();
+ if (mRemoteAddr != null) {
+ sRemoteDeviceName = mBluetooth.getRemoteName(mRemoteAddr);
+ // In case getRemoteName failed and return null
+ if (sRemoteDeviceName == null) {
+ sRemoteDeviceName = getString(R.string.defaultname);
+ }
+ }
+ // TODO: have dependency on device trust feature
+ // implementation
+ // trust = mBluetooth.getTrustState(mRemoteAddr);
+ if (trust) {
+ try {
+ startObexServerSession();
+ } catch (IOException ex) {
+ Log.e(TAG, "catch exception starting obex server session"
+ + ex.toString());
+ }
+ } else {
+ BluetoothPbapReceiver.makeNewPbapNotification(getApplicationContext(),
+ ACCESS_REQUEST);
+ Log.i(TAG, "incomming connection accepted from" + sRemoteDeviceName);
+ mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
+ .obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
+ }
+ stopped = true; // job done ,close this thread;
+ }
+ }
+ }
+
+ void shutdown() {
+ stopped = true;
+ interrupt();
+ }
+ }
+
+ private void startRfcommSocketListener() {
+ if (mServerSocket == null) {
+ if (!initSocket()) {
+ closeService();
+ return;
+ }
+ }
+ if (mAcceptThread == null) {
+ mAcceptThread = new SocketAcceptThread();
+ mAcceptThread.setName("BluetoothPbapAcceptThread");
+ mAcceptThread.start();
+ }
+ }
+
+ private void setState(int state) {
+ // TODO: have dependency on framework/base
+ // setState(state, BluetoothHeadset.RESULT_SUCCESS);
+ }
+
+ // TODO: have dependency on framework/base
+ /*
+ * private synchronized void setState(int state, int result) { if (state !=
+ * mState) { if (DBG) Log.d(TAG, "Pbap state " + mState + " -> " + state +
+ * ", result = " + result); Intent intent = new
+ * Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION);
+ * intent.putExtra(BluetoothPbap.PBAP_PREVIOUS_STATE, mState); mState =
+ * state; intent.putExtra(BluetoothPbap.PBAP_STATE, mState);
+ * intent.putExtra(BluetoothIntent.ADDRESS, mRemoteAddr);
+ * sendBroadcast(intent, BLUETOOTH_PERM); } }
+ */
+
+ /**
+ * Handlers for incoming service calls
+ */
+ // TODO: have dependency on framework/base
+ /*
+ private final IBluetoothPbap.Stub mBinder = new IBluetoothPbap.Stub() {
+ private static final String TAG1 = "IBluetoothPbap.Stub";
+
+ public int getState() {
+ if (DBG) {
+ Log.d(TAG1, "getState " + mState);
+ }
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mState;
+ }
+
+ public String getPceAddress() {
+ if (DBG) {
+ Log.d(TAG1, "getPceAddress" + mRemoteAddr);
+ }
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ if (mState == BluetoothPbap.STATE_DISCONNECTED) {
+ return null;
+ }
+ return mRemoteAddr;
+ }
+
+ public boolean isConnected(String address) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return mState == BluetoothPbap.STATE_CONNECTED && mRemoteAddr.equals(address);
+ }
+
+ public boolean connectPce(String address) {
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+ return false;
+ }
+
+ public void disconnectPce() {
+ if (DBG) {
+ Log.d(TAG1, "disconnectPce");
+ }
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+ synchronized (BluetoothPbapService.this) {
+ switch (mState) {
+ case BluetoothPbap.STATE_CONNECTED:
+ if (mServerSession != null) {
+ mServerSession.close();
+ mServerSession = null;
+ }
+ try {
+ closeSocket(false, true);
+ } catch (IOException ex) {
+ Log.e(TAG1, "Caught the error: " + ex);
+ }
+ setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
+ break;
+ }
+ }
+ }
+
+ // Not used
+ public boolean setPriority(String address, int priority) {
+ if (DBG) {
+ Log.d(TAG1, "setPriority");
+ }
+ enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
+ return true;
+ }
+
+ // Not used
+ public int getPriority(String address) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ return 0;
+ }
+ };
+*/
+ private final Handler mSessionStatusHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case START_LISTENER:
+ if (mBluetooth.isEnabled()) {
+ startRfcommSocketListener();
+ } else {
+ closeService();// release all resources
+ }
+ break;
+ case USER_TIMEOUT:
+ Intent intent = new Intent(USER_CONFIRM_TIMEOUT);
+ sendBroadcast(intent);
+ BluetoothPbapReceiver.removePbapNotification(getApplicationContext(),
+ BluetoothPbapReceiver.NOTIFICATION_ID_ACCESS);
+ obexServerSessionClose();
+ break;
+ case AUTH_TIMEOUT:
+ Intent i = new Intent(USER_CONFIRM_TIMEOUT);
+ sendBroadcast(i);
+ BluetoothPbapReceiver.removePbapNotification(getApplicationContext(),
+ BluetoothPbapReceiver.NOTIFICATION_ID_AUTH);
+ notifyAuthCancelled();
+ break;
+ case MSG_SERVERSESSION_CLOSE:
+ obexServerSessionClose();
+ mTmpTxt = getString(R.string.toast_disconnected, sRemoteDeviceName);
+ Toast.makeText(BluetoothPbapService.this, mTmpTxt, Toast.LENGTH_SHORT).show();
+ break;
+ case MSG_SESSION_ESTABLISHED:
+ mTmpTxt = getString(R.string.toast_connected, sRemoteDeviceName);
+ Toast.makeText(BluetoothPbapService.this, mTmpTxt, Toast.LENGTH_SHORT).show();
+ break;
+ case MSG_SESSION_DISCONNECTED:
+ // case MSG_SERVERSESSION_CLOSE will handle ,so just skip
+ break;
+ case MSG_OBEX_AUTH_CHALL:
+ BluetoothPbapReceiver.makeNewPbapNotification(getApplicationContext(),
+ AUTH_CHALL);
+ mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
+ .obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
+ break;
+ }
+ }
+ };
+
+}
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
new file mode 100644
index 000000000..27c118e9f
--- /dev/null
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
@@ -0,0 +1,511 @@
+
+package com.android.bluetooth.pbap;
+
+import com.android.bluetooth.R;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Contacts;
+import android.provider.CallLog.Calls;
+import android.provider.Contacts.Organizations;
+import android.provider.CallLog;
+import android.provider.Contacts.People;
+import android.provider.Contacts.Phones;
+import android.syncml.pim.vcard.ContactStruct;
+import android.syncml.pim.vcard.VCardComposer;
+import android.syncml.pim.vcard.VCardException;
+import android.syncml.pim.vcard.VCardParser;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class BluetoothPbapVcardManager {
+ static private final String TAG = "BluetoothPbapVcardManager";
+
+ private ContentResolver mResolver;
+
+ private Context mContext;
+
+ private String mDefaultName = null;
+
+ private String mDefaultNumber = null;
+
+ /** The projection to use when querying the call log table */
+ public static final String[] CALL_LOG_PROJECTION = new String[] {
+ Calls._ID, Calls.NUMBER, Calls.DATE, Calls.DURATION, Calls.TYPE, Calls.CACHED_NAME,
+ Calls.CACHED_NUMBER_TYPE, Calls.CACHED_NUMBER_LABEL
+ };
+
+ public static final int ID_COLUMN_INDEX = 0;
+
+ public static final int NUMBER_COLUMN_INDEX = 1;
+
+ public static final int DATE_COLUMN_INDEX = 2;
+
+ public static final int DURATION_COLUMN_INDEX = 3;
+
+ public static final int CALL_TYPE_COLUMN_INDEX = 4;
+
+ public static final int CALLER_NAME_COLUMN_INDEX = 5;
+
+ public static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 6;
+
+ public static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 7;
+
+ /** The projection to use when querying the phone book table */
+ public static final String[] CONTACT_PROJECTION = new String[] {
+ People._ID, // 0
+ People.NAME, // 1
+ People.NOTES, // 2
+ People.PRIMARY_PHONE_ID, // 3
+ People.PRESENCE_STATUS, // 4
+ People.STARRED, // 5
+ People.CUSTOM_RINGTONE, // 6
+ People.SEND_TO_VOICEMAIL, // 7
+ People.PHONETIC_NAME, // 8
+ };
+
+ public static final int CONTACT_ID_COLUMN = 0;
+
+ public static final int CONTACT_NAME_COLUMN = 1;
+
+ public static final int CONTACT_NOTES_COLUMN = 2;
+
+ public static final int CONTACT_PREFERRED_PHONE_COLUMN = 3;
+
+ public static final int CONTACT_SERVER_STATUS_COLUMN = 4;
+
+ public static final int CONTACT_STARRED_COLUMN = 5;
+
+ public static final int CONTACT_CUSTOM_RINGTONE_COLUMN = 6;
+
+ public static final int CONTACT_SEND_TO_VOICEMAIL_COLUMN = 7;
+
+ public static final int CONTACT_PHONETIC_NAME_COLUMN = 8;
+
+ public static final String[] PHONES_PROJECTION = new String[] {
+ People.Phones._ID, // 0
+ People.Phones.NUMBER, // 1
+ People.Phones.TYPE, // 2
+ People.Phones.LABEL, // 3
+ People.Phones.ISPRIMARY, // 4
+ };
+
+ public static final int PHONES_ID_COLUMN = 0;
+
+ public static final int PHONES_NUMBER_COLUMN = 1;
+
+ public static final int PHONES_TYPE_COLUMN = 2;
+
+ public static final int PHONES_LABEL_COLUMN = 3;
+
+ public static final int PHONES_ISPRIMARY_COLUMN = 4;
+
+ public static final String[] METHODS_PROJECTION = new String[] {
+ People.ContactMethods._ID, // 0
+ People.ContactMethods.KIND, // 1
+ People.ContactMethods.DATA, // 2
+ People.ContactMethods.TYPE, // 3
+ People.ContactMethods.LABEL, // 4
+ People.ContactMethods.ISPRIMARY, // 5
+ People.ContactMethods.AUX_DATA, // 6
+ };
+
+ public static final int METHODS_ID_COLUMN = 0;
+
+ public static final int METHODS_KIND_COLUMN = 1;
+
+ public static final int METHODS_DATA_COLUMN = 2;
+
+ public static final int METHODS_TYPE_COLUMN = 3;
+
+ public static final int METHODS_LABEL_COLUMN = 4;
+
+ public static final int METHODS_ISPRIMARY_COLUMN = 5;
+
+ public static final int METHODS_AUX_DATA_COLUMN = 6;
+
+ public static final int METHODS_STATUS_COLUMN = 7;
+
+ public static final String[] ORGANIZATIONS_PROJECTION = new String[] {
+ Organizations._ID, // 0
+ Organizations.TYPE, // 1
+ Organizations.LABEL, // 2
+ Organizations.COMPANY, // 3
+ Organizations.TITLE, // 4
+ Organizations.ISPRIMARY, // 5
+ };
+
+ public static final int ORGANIZATIONS_ID_COLUMN = 0;
+
+ public static final int ORGANIZATIONS_TYPE_COLUMN = 1;
+
+ public static final int ORGANIZATIONS_LABEL_COLUMN = 2;
+
+ public static final int ORGANIZATIONS_COMPANY_COLUMN = 3;
+
+ public static final int ORGANIZATIONS_TITLE_COLUMN = 4;
+
+ public static final int ORGANIZATIONS_ISPRIMARY_COLUMN = 5;
+
+ public BluetoothPbapVcardManager(final Context context) {
+ mContext = context;
+ mResolver = mContext.getContentResolver();
+ mDefaultName = context.getString(android.R.string.unknownName);
+ mDefaultNumber = context.getString(R.string.defaultnumber);
+ }
+
+ private String getThisPhoneName() {
+ String name = "";
+ name = BluetoothPbapService.getLocalPhoneName();
+ if (name == null || name.trim().length() == 0) {
+ name = mDefaultName;
+ }
+ return name;
+ }
+
+ private String getThisPhoneNumber() {
+ String number = "";
+ number = BluetoothPbapService.getLocalPhoneNum();
+ if (number == null || number.trim().length() == 0) {
+ number = mDefaultNumber;
+ }
+ return number;
+ }
+
+ public final int getPhonebookSize() {
+ Uri myUri = Contacts.People.CONTENT_URI;
+ int mPhonebookSize = 0;
+ Cursor contactC = mResolver.query(myUri, null, null, null, null);
+ if (contactC != null) {
+ mPhonebookSize = contactC.getCount() + 1; // always has the 0.vcf
+ }
+ return mPhonebookSize;
+ }
+
+ public final String getPhonebook(final int pos, final boolean vCard21) {
+ int size = getPhonebookSize();
+ long id = 0;
+ try {
+ if (pos >= 0 && pos < size) {
+ Uri myUri = Contacts.People.CONTENT_URI;
+ // Individual Contact may be deleted,but Uri in database is also
+ // removed.So we need to calculate the actual Uri
+ if (pos > 0) {
+ Cursor personCursor = mResolver.query(myUri, CONTACT_PROJECTION, null, null,
+ null);
+ if (personCursor != null) {
+ personCursor.moveToPosition(pos - 1);
+ id = personCursor.getLong(CONTACT_ID_COLUMN);
+ personCursor.close();
+ }
+ String sPos = String.valueOf(id);
+ myUri = Uri.withAppendedPath(myUri, sPos);
+ }
+ return loadPhonebook(myUri, pos, vCard21);
+ } else {
+ Log.w(TAG, "pos invalid");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "catch exception" + e.toString());
+ }
+ return null;
+ }
+
+ public final int getCallHistorySize(final int type) {
+ int size = 0;
+ Uri myUri = CallLog.Calls.CONTENT_URI;
+ String selection = null;
+ switch (type) {
+ case BluetoothPbapObexServer.NEED_INCOMING_CALL_NUMBER:
+ selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE;
+ break;
+ case BluetoothPbapObexServer.NEED_OUTGOING_CALL_NUMBER:
+ selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
+ break;
+ case BluetoothPbapObexServer.NEED_MISSED_CALL_NUMBER:
+ selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
+ break;
+ default:
+ break;
+ }
+ Cursor callCursor = mResolver.query(myUri, null, selection, null,
+ CallLog.Calls.DEFAULT_SORT_ORDER);
+ if (callCursor != null) {
+ size = callCursor.getCount();
+ callCursor.close();
+ }
+ return size;
+ }
+
+ public final String getCallHistory(final int pos, final int type, final boolean vCard21) {
+ int size = 0;
+ String selection = null;
+ switch (type) {
+ case BluetoothPbapObexServer.NEED_INCOMING_CALL_NUMBER:
+ selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE;
+ break;
+ case BluetoothPbapObexServer.NEED_OUTGOING_CALL_NUMBER:
+ selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
+ break;
+ case BluetoothPbapObexServer.NEED_MISSED_CALL_NUMBER:
+ selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
+ break;
+ default:
+ break;
+ }
+ size = getCallHistorySize(type);
+ try {
+ if (pos >= 0 && pos < size) {
+ Uri myUri = CallLog.Calls.CONTENT_URI;
+ return loadCallHistory(myUri, pos, selection, vCard21);
+ } else {
+ Log.w(TAG, "pos invalid");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "catch exception e" + e.toString());
+ }
+
+ return null;
+ }
+
+ public final ArrayList<String> loadNameList() {
+ ArrayList<String> nameList = new ArrayList<String>();
+ int size = getPhonebookSize();
+ Uri myUri = Contacts.People.CONTENT_URI;
+ Cursor contactC = mResolver.query(myUri, null, null, null, null);
+ for (int pos = 0; pos < size; pos++) {
+ if (pos == 0) {
+ nameList.add(getThisPhoneName());
+ } else {
+ if (contactC != null) {
+ contactC.moveToPosition(pos - 1);
+ String name = contactC.getString(contactC
+ .getColumnIndexOrThrow(Contacts.People.NAME));
+ if (name == null || name.trim().length() == 0) {
+ name = mDefaultName;
+ }
+ nameList.add(name);
+ }
+ }
+ }
+ if (contactC != null) {
+ contactC.close();
+ }
+ return nameList;
+ }
+
+ public final ArrayList<String> loadNumberList() {
+ ArrayList<String> numberList = new ArrayList<String>();
+ if (numberList.size() > 0) {
+ numberList.clear();
+ }
+ int size = getPhonebookSize();
+ Uri myUri = Contacts.People.CONTENT_URI;
+ Cursor contactC = mResolver.query(myUri, null, null, null, null);
+ for (int pos = 0; pos < size; pos++) {
+ if (pos == 0) {
+ numberList.add(getThisPhoneNumber());
+ } else {
+ contactC.moveToPosition(pos - 1);
+ String number = contactC.getString(contactC
+ .getColumnIndexOrThrow(Contacts.People.PRIMARY_PHONE_ID));
+ if (number == null || number.trim().length() == 0) {
+ number = mDefaultNumber;
+ }
+ numberList.add(number);
+ }
+ }
+ return numberList;
+ }
+
+ public final ArrayList<String> loadCallHistoryList(final int type) {
+ int size = 0;
+ String selection = null;
+ Uri myUri = CallLog.Calls.CONTENT_URI;
+ ArrayList<String> list = new ArrayList<String>();
+ switch (type) {
+ case BluetoothPbapObexServer.NEED_INCOMING_CALL_NUMBER:
+ selection = Calls.TYPE + "=" + CallLog.Calls.INCOMING_TYPE;
+ break;
+ case BluetoothPbapObexServer.NEED_OUTGOING_CALL_NUMBER:
+ selection = Calls.TYPE + "=" + CallLog.Calls.OUTGOING_TYPE;
+ break;
+ case BluetoothPbapObexServer.NEED_MISSED_CALL_NUMBER:
+ selection = Calls.TYPE + "=" + CallLog.Calls.MISSED_TYPE;
+ break;
+ default:
+ break;
+ }
+ size = getCallHistorySize(type);
+ Cursor callCursor = mResolver.query(myUri, CALL_LOG_PROJECTION, selection, null,
+ CallLog.Calls.DEFAULT_SORT_ORDER);
+ if (callCursor != null) {
+ for (int pos = 0; pos < size; pos++) {
+ callCursor.moveToPosition(pos);
+ String name = callCursor.getString(CALLER_NAME_COLUMN_INDEX);
+ if (name == null || name.trim().length() == 0) {
+ // name not found,use number instead
+ name = callCursor.getString(NUMBER_COLUMN_INDEX);
+ }
+ list.add(name);
+ }
+ callCursor.close();
+ }
+ return list;
+ }
+
+ private final String loadCallHistory(final Uri mUri, final int pos, final String selection,
+ final boolean vCard21) {
+ ContactStruct contactStruct = new ContactStruct();
+ Cursor callCursor = mResolver.query(mUri, CALL_LOG_PROJECTION, selection, null,
+ CallLog.Calls.DEFAULT_SORT_ORDER);
+ if (callCursor != null) {
+ if (callCursor.moveToPosition(pos)) {
+ contactStruct.name = callCursor.getString(CALLER_NAME_COLUMN_INDEX);
+ if (contactStruct.name == null || contactStruct.name.trim().length() == 0) {
+ contactStruct.name = callCursor.getString(NUMBER_COLUMN_INDEX);
+ }
+ String number = callCursor.getString(NUMBER_COLUMN_INDEX);
+ int type = callCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX);
+ String label = callCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX);
+ if (label == null || label.trim().length() == 0) {
+ label = Integer.toString(type);
+ }
+ contactStruct.addPhone(type, number, label, true);
+ }
+ callCursor.close();
+ }
+ try {
+ VCardComposer composer = new VCardComposer();
+ if (vCard21) {
+ return composer.createVCard(contactStruct, VCardParser.VERSION_VCARD21_INT);
+ } else {
+ return composer.createVCard(contactStruct, VCardParser.VERSION_VCARD30_INT);
+ }
+ } catch (VCardException e) {
+ Log.e(TAG, "catch exception" + e.toString());
+ return null;
+ }
+ }
+
+ private final String loadPhonebook(final Uri mUri, final int pos, final boolean vCard21) {
+ // Build up the phone entries
+ ContactStruct contactStruct = new ContactStruct();
+ Cursor personCursor = mResolver.query(mUri, CONTACT_PROJECTION, null, null, null);
+ if (personCursor == null) {
+ return null;
+ }
+ if (pos == 0) {
+ contactStruct.name = getThisPhoneName();
+ contactStruct.addPhone(Contacts.PhonesColumns.TYPE_MOBILE, getThisPhoneNumber(), "",
+ true);
+ } else if (personCursor.moveToNext()) {
+ contactStruct.name = personCursor.getString(CONTACT_NAME_COLUMN);
+ if (contactStruct.name == null || contactStruct.name.trim().length() == 0) {
+ contactStruct.name = mDefaultName;
+ }
+ contactStruct.notes.add(checkStrEnd(personCursor.getString(CONTACT_NOTES_COLUMN),
+ vCard21));
+ final Uri phonesUri = Uri.withAppendedPath(mUri, People.Phones.CONTENT_DIRECTORY);
+ final Cursor phonesCursor = mResolver.query(phonesUri, PHONES_PROJECTION, null, null,
+ Phones.ISPRIMARY + " DESC");
+ if (phonesCursor != null) {
+ while (phonesCursor.moveToNext()) {
+ int type = phonesCursor.getInt(PHONES_TYPE_COLUMN);
+ String number = phonesCursor.getString(PHONES_NUMBER_COLUMN);
+ String label = phonesCursor.getString(PHONES_LABEL_COLUMN);
+ contactStruct.addPhone(type, number, label, true);
+ }
+ phonesCursor.close();
+ }
+ // Build the contact method entries
+ final Uri methodsUri = Uri.withAppendedPath(mUri,
+ People.ContactMethods.CONTENT_DIRECTORY);
+ Cursor methodsCursor = mResolver
+ .query(methodsUri, METHODS_PROJECTION, null, null, null);
+ if (methodsCursor != null) {
+ while (methodsCursor.moveToNext()) {
+ int kind = methodsCursor.getInt(METHODS_KIND_COLUMN);
+ String label = methodsCursor.getString(METHODS_LABEL_COLUMN);
+ String data = methodsCursor.getString(METHODS_DATA_COLUMN);
+ int type = methodsCursor.getInt(METHODS_TYPE_COLUMN);
+ boolean isPrimary = methodsCursor.getInt(METHODS_ISPRIMARY_COLUMN) == 1 ? true
+ : false;
+ // TODO
+ // Below code is totally depend on the implementation of
+ // package android.syncml.pim.vcard
+ // VcardComposer shall throw null pointer exception when
+ // label is not set
+ // function appendContactMethodStr is weak and shall be
+ // improved in the future
+ if (kind == Contacts.KIND_EMAIL) {
+ if (type == Contacts.ContactMethodsColumns.TYPE_OTHER) {
+ if (label == null || label.trim().length() == 0) {
+ label = Integer.toString(type);
+ }
+ }
+ }
+ if (kind == Contacts.KIND_POSTAL) {
+ data = checkStrEnd(data, vCard21);
+ }
+ contactStruct.addContactmethod(kind, type, data, label, isPrimary);
+ }
+ methodsCursor.close();
+ }
+
+ // Build the organization entries
+ final Uri organizationsUri = Uri
+ .withAppendedPath(mUri, Organizations.CONTENT_DIRECTORY);
+ Cursor organizationsCursor = mResolver.query(organizationsUri,
+ ORGANIZATIONS_PROJECTION, "isprimary", null, null);
+
+ if (organizationsCursor != null) {
+ while (organizationsCursor.moveToNext()) {
+ int type = organizationsCursor.getInt(ORGANIZATIONS_TYPE_COLUMN);
+ String company = organizationsCursor.getString(ORGANIZATIONS_COMPANY_COLUMN);
+ String title = checkStrEnd(organizationsCursor
+ .getString(ORGANIZATIONS_TITLE_COLUMN), vCard21);
+ boolean isPrimary = organizationsCursor.getInt(ORGANIZATIONS_ISPRIMARY_COLUMN) == 1 ? true
+ : false;
+ contactStruct.addOrganization(type, company, title, isPrimary);
+ }
+ organizationsCursor.close();
+ }
+ }
+ personCursor.close();
+ // Generate vCard data.
+ try {
+ VCardComposer composer = new VCardComposer();
+ if (vCard21) {
+ return composer.createVCard(contactStruct, VCardParser.VERSION_VCARD21_INT);
+ } else {
+ return composer.createVCard(contactStruct, VCardParser.VERSION_VCARD30_INT);
+ }
+ } catch (VCardException e) {
+ Log.e(TAG, "catch exception in loadPhonebook" + e.toString());
+ return null;
+ }
+ }
+
+ /**
+ * This function is to check the string end to avoid function
+ * foldingString's returning null in package android.syncml.pim.vcard
+ */
+ private final String checkStrEnd(String str, final boolean vCard21) {
+ if (str == null) {
+ return str;
+ }
+ if (str.charAt(str.length() - 1) != '\n') {
+ if (vCard21) {
+ str += "\r\n";
+ } else {
+ str += "\n";
+ }
+ }
+ return str;
+ }
+
+}