blob: af38eb359be3ed3c56e684b7d3fe6e48462377fb [file] [log] [blame]
/*
* Copyright (c) 2008-2009, Motorola, Inc.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of the Motorola, Inc. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.android.bluetooth.pbap;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothPbap;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothPbap;
import android.database.sqlite.SQLiteException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContentResolver;
import android.database.ContentObserver;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder;
import com.android.bluetooth.IObexConnectionHandler;
import com.android.bluetooth.ObexServerSockets;
import com.android.bluetooth.R;
import com.android.bluetooth.sdp.SdpManager;
import com.android.bluetooth.Utils;
import com.android.bluetooth.util.DevicePolicyUtils;
import java.io.IOException;
import java.util.Calendar;
import java.util.concurrent.atomic.AtomicLong;
import java.util.HashMap;
import javax.obex.ServerSession;
public class BluetoothPbapService extends ProfileService implements IObexConnectionHandler {
private static final String TAG = "BluetoothPbapService";
/**
* To enable PBAP DEBUG/VERBOSE logging - run below cmd in adb shell, and
* restart com.android.bluetooth process. only enable DEBUG log:
* "setprop log.tag.BluetoothPbapService DEBUG"; enable both VERBOSE and
* DEBUG log: "setprop log.tag.BluetoothPbapService VERBOSE"
*/
public static final boolean DEBUG = true;
public static final boolean VERBOSE = false;
/**
* Intent indicating incoming obex authentication request which is from
* PCE(Carkit)
*/
public static final String AUTH_CHALL_ACTION = "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_ACTION = "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_ACTION = "com.android.bluetooth.pbap.authcancelled";
/**
* Intent indicating timeout for user confirmation, which is sent to
* BluetoothPbapActivity
*/
public static final String USER_CONFIRM_TIMEOUT_ACTION =
"com.android.bluetooth.pbap.userconfirmtimeout";
/**
* Intent Extra name indicating session key which is sent from
* BluetoothPbapActivity
*/
public static final String EXTRA_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;
public static final int MSG_ACQUIRE_WAKE_LOCK = 5004;
public static final int MSG_RELEASE_WAKE_LOCK = 5005;
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 static final int SHUTDOWN = 4;
protected static final int LOAD_CONTACTS = 5;
private static final int CHECK_SECONDARY_VERSION_COUNTER = 6;
protected static final int ROLLOVER_COUNTERS = 7;
private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000;
private static final int RELEASE_WAKE_LOCK_DELAY = 10000;
// 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 String PBAP_NOTIFICATION_CHANNEL = "pbap_notification_channel";
private PowerManager.WakeLock mWakeLock = null;
private BluetoothPbapAuthenticator mAuth = null;
private BluetoothPbapObexServer mPbapServer;
private ServerSession mServerSession = null;
private BluetoothServerSocket mServerSocket = null;
private BluetoothSocket mConnSocket = null;
private BluetoothDevice mRemoteDevice = null;
private static String sLocalPhoneNum = null;
private static String sLocalPhoneName = null;
private static String sRemoteDeviceName = null;
private volatile boolean mInterrupted;
private int mState;
private boolean mIsWaitingAuthorization = false;
private ObexServerSockets mServerSockets = null;
private static final int SDP_PBAP_SERVER_VERSION = 0x0102;
private static final int SDP_PBAP_SUPPORTED_REPOSITORIES = 0x0003;
private static final int SDP_PBAP_SUPPORTED_FEATURES = 0x021F;
private AlarmManager mAlarmManager = null;
private int mSdpHandle = -1;
private boolean mRemoveTimeoutMsg = false;
private int mPermission = BluetoothDevice.ACCESS_UNKNOWN;
private boolean mSdpSearchInitiated = false;
private boolean isRegisteredObserver = false;
protected Context mContext;
// package and class name to which we send intent to check phone book 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 class BluetoothPbapContentObserver extends ContentObserver {
public BluetoothPbapContentObserver() {
super(new Handler());
}
@Override
public void onChange(boolean selfChange) {
Log.d(TAG, " onChange on contact uri ");
if (BluetoothPbapUtils.contactsLoaded) {
if (!mSessionStatusHandler.hasMessages(CHECK_SECONDARY_VERSION_COUNTER)) {
mSessionStatusHandler.sendMessage(
mSessionStatusHandler.obtainMessage(CHECK_SECONDARY_VERSION_COUNTER));
}
}
}
}
private BluetoothPbapContentObserver mContactChangeObserver;
public BluetoothPbapService() {
mState = BluetoothPbap.STATE_DISCONNECTED;
mContext = this;
}
// process the intent from receiver
private void parseIntent(final Intent intent) {
String action = intent.getAction();
if (DEBUG) Log.d(TAG, "action: " + action);
if (action == null) return; // Nothing to do
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if (DEBUG) Log.d(TAG, "state: " + 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)) {
mSessionStatusHandler.removeMessages(USER_TIMEOUT);
mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
}
// Release all resources
closeService();
} else if (state == BluetoothAdapter.STATE_ON) {
// start RFCOMM listener
mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
}
return;
}
if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (mRemoteDevice == null) return;
if (DEBUG) Log.d(TAG,"ACL disconnected for "+ device);
if (mIsWaitingAuthorization && mRemoteDevice.equals(device)) {
mSessionStatusHandler.removeMessages(USER_TIMEOUT);
mSessionStatusHandler.obtainMessage(USER_TIMEOUT).sendToTarget();
}
mSessionStatusHandler.obtainMessage(MSG_SERVERSESSION_CLOSE).sendToTarget();
return;
}
if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
if ((!mIsWaitingAuthorization)
|| (requestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS)) {
// this reply is not for us
return;
}
mSessionStatusHandler.removeMessages(USER_TIMEOUT);
mIsWaitingAuthorization = false;
if (intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
BluetoothDevice.CONNECTION_ACCESS_NO)
== BluetoothDevice.CONNECTION_ACCESS_YES) {
if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
boolean result = mRemoteDevice.setPhonebookAccessPermission(
BluetoothDevice.ACCESS_ALLOWED);
if (VERBOSE) {
Log.v(TAG, "setPhonebookAccessPermission(ACCESS_ALLOWED)=" + result);
}
}
try {
if (mConnSocket != null) {
startObexServerSession();
} else {
mSessionStatusHandler.obtainMessage(MSG_SERVERSESSION_CLOSE).sendToTarget();
}
} catch (IOException ex) {
Log.e(TAG, "Caught the error: " + ex.toString());
}
} else {
if (intent.getBooleanExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, false)) {
boolean result = mRemoteDevice.setPhonebookAccessPermission(
BluetoothDevice.ACCESS_REJECTED);
if (VERBOSE) {
Log.v(TAG, "setPhonebookAccessPermission(ACCESS_REJECTED)=" + result);
}
}
mSessionStatusHandler.obtainMessage(MSG_SERVERSESSION_CLOSE).sendToTarget();
}
return;
}
if (action.equals(AUTH_RESPONSE_ACTION)) {
String sessionkey = intent.getStringExtra(EXTRA_SESSION_KEY);
notifyAuthKeyInput(sessionkey);
} else if (action.equals(AUTH_CANCELLED_ACTION)) {
notifyAuthCancelled();
} else {
Log.w(TAG, "Unrecognized intent!");
return;
}
mSessionStatusHandler.removeMessages(USER_TIMEOUT);
}
private BroadcastReceiver mPbapReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
parseIntent(intent);
}
};
private final boolean initSocket() {
if (VERBOSE) Log.v(TAG, "Pbap Service initSocket");
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++) {
initSocketOK = true;
try {
// It is mandatory for PSE to support initiation of bonding and
// encryption.
mServerSocket = mAdapter.listenUsingEncryptedRfcommWithServiceRecord
("OBEX Phonebook Access Server", BluetoothUuid.PBAP_PSE.getUuid());
} catch (IOException e) {
Log.e(TAG, "Error create RfcommServerSocket " + e.toString());
initSocketOK = false;
}
if (!initSocketOK) {
// Need to break out of this loop if BT is being turned off.
if (mAdapter == null) break;
int state = mAdapter.getState();
if ((state != BluetoothAdapter.STATE_TURNING_ON) &&
(state != BluetoothAdapter.STATE_ON)) {
Log.w(TAG, "initServerSocket failed as BT is (being) turned off");
break;
}
try {
if (VERBOSE) Log.v(TAG, "wait 300 ms");
Thread.sleep(300);
} catch (InterruptedException e) {
Log.e(TAG, "socketAcceptThread thread was interrupted (3)");
break;
}
} 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 ");
} else {
Log.e(TAG, "Error to create listening socket after " + CREATE_RETRY_TIME + " try");
}
return initSocketOK;
}
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);
}
}
}
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, "Pbap Service closeService in");
BluetoothPbapUtils.savePbapParams(this, BluetoothPbapUtils.primaryVersionCounter,
BluetoothPbapUtils.secondaryVersionCounter, BluetoothPbapUtils.mDbIdentifier.get(),
BluetoothPbapUtils.contactsLastUpdated, BluetoothPbapUtils.totalFields,
BluetoothPbapUtils.totalSvcFields, BluetoothPbapUtils.totalContacts);
// exit initSocket early
mInterrupted = true;
if (mWakeLock != null) {
mWakeLock.release();
mWakeLock = null;
}
if (mServerSession != null) {
mServerSession.close();
mServerSession = null;
}
closeConnectionSocket();
closeServerSocket();
if (mServerSockets != null) {
mServerSockets.shutdown(false);
mServerSockets = null;
}
if (mSessionStatusHandler != null) mSessionStatusHandler.removeCallbacksAndMessages(null);
if (VERBOSE) Log.v(TAG, "Pbap Service closeService out");
}
private final void startObexServerSession() throws IOException {
if (VERBOSE) Log.v(TAG, "Pbap Service startObexServerSession");
// 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();
if (TextUtils.isEmpty(sLocalPhoneName)) {
sLocalPhoneName = this.getString(R.string.localPhoneName);
}
}
mPbapServer = new BluetoothPbapObexServer(mSessionStatusHandler, this);
synchronized (this) {
mAuth = new BluetoothPbapAuthenticator(mSessionStatusHandler);
mAuth.setChallenged(false);
mAuth.setCancelled(false);
}
BluetoothObexTransport transport = new BluetoothObexTransport(mConnSocket);
mServerSession = new ServerSession(transport, mPbapServer, mAuth);
setState(BluetoothPbap.STATE_CONNECTED);
mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
.obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
if (VERBOSE) {
Log.v(TAG, "startObexServerSession() success!");
}
}
private void stopObexServerSession() {
if (VERBOSE) Log.v(TAG, "Pbap Service stopObexServerSession");
mSessionStatusHandler.removeMessages(MSG_ACQUIRE_WAKE_LOCK);
mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
// Release the wake lock if obex transaction is over
if (mWakeLock != null) {
mWakeLock.release();
mWakeLock = null;
}
if (mServerSession != null) {
mServerSession.close();
mServerSession = null;
}
closeConnectionSocket();
// Last obex transaction is finished, we start to listen for incoming
// connection again
if (mAdapter != null && mAdapter.isEnabled()) {
startSocketListeners();
}
setState(BluetoothPbap.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
* 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;
@Override
public void run() {
BluetoothServerSocket serverSocket;
if (mServerSocket == null) {
if (!initSocket()) {
return;
}
}
while (!stopped) {
try {
if (VERBOSE) Log.v(TAG, "Accepting socket connection...");
serverSocket = mServerSocket;
if (serverSocket == null) {
Log.w(TAG, "mServerSocket is null");
break;
}
mConnSocket = serverSocket.accept();
if (VERBOSE) Log.v(TAG, "Accepted socket connection...");
synchronized (BluetoothPbapService.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);
}
int permission = mRemoteDevice.getPhonebookAccessPermission();
if (VERBOSE) Log.v(TAG, "getPhonebookAccessPermission() = " + permission);
if (permission == BluetoothDevice.ACCESS_ALLOWED) {
try {
if (VERBOSE) {
Log.v(TAG, "incoming connection accepted from: " + sRemoteDeviceName
+ " automatically as already allowed device");
}
startObexServerSession();
} catch (IOException ex) {
Log.e(TAG, "Caught exception starting obex server session"
+ ex.toString());
}
} else if (permission == BluetoothDevice.ACCESS_REJECTED) {
if (VERBOSE) {
Log.v(TAG, "incoming connection rejected from: " + sRemoteDeviceName
+ " automatically as already rejected device");
}
stopObexServerSession();
} else { // permission == BluetoothDevice.ACCESS_UNKNOWN
// Send an Intent to Settings app to ask user preference.
Intent intent =
new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
intent.setPackage(getString(R.string.pairing_ui_package));
intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName());
intent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, getName());
mIsWaitingAuthorization = true;
sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM);
if (VERBOSE) Log.v(TAG, "waiting for authorization for connection from: "
+ sRemoteDeviceName);
// In case car kit time out and try to use HFP for
// phonebook
// access, while UI still there waiting for user to
// confirm
mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
.obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
// We will continue the process when we receive
// BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY from Settings app.
}
stopped = true; // job done ,close this thread;
} catch (IOException ex) {
stopped=true;
/*
if (stopped) {
break;
}
*/
if (VERBOSE) Log.v(TAG, "Accept exception: " + ex.toString());
}
}
}
void shutdown() {
stopped = true;
interrupt();
}
}
protected final Handler mSessionStatusHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (VERBOSE) Log.v(TAG, "Handler(): got msg=" + msg.what);
switch (msg.what) {
case START_LISTENER:
if (mAdapter.isEnabled()) {
startSocketListeners();
}
break;
case USER_TIMEOUT:
Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
intent.setPackage(getString(R.string.pairing_ui_package));
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
mIsWaitingAuthorization = false;
stopObexServerSession();
break;
case AUTH_TIMEOUT:
Intent i = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
sendBroadcast(i);
removePbapNotification(NOTIFICATION_ID_AUTH);
notifyAuthCancelled();
break;
case MSG_SERVERSESSION_CLOSE:
stopObexServerSession();
break;
case MSG_SESSION_ESTABLISHED:
break;
case MSG_SESSION_DISCONNECTED:
// case MSG_SERVERSESSION_CLOSE will handle ,so just skip
break;
case MSG_OBEX_AUTH_CHALL:
createPbapNotification(AUTH_CHALL_ACTION);
mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
.obtainMessage(AUTH_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
break;
case MSG_ACQUIRE_WAKE_LOCK:
if (mWakeLock == null) {
PowerManager pm = (PowerManager)getSystemService(
Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"StartingObexPbapTransaction");
mWakeLock.setReferenceCounted(false);
mWakeLock.acquire();
Log.w(TAG, "Acquire Wake Lock");
}
mSessionStatusHandler.removeMessages(MSG_RELEASE_WAKE_LOCK);
mSessionStatusHandler.sendMessageDelayed(mSessionStatusHandler
.obtainMessage(MSG_RELEASE_WAKE_LOCK), RELEASE_WAKE_LOCK_DELAY);
break;
case MSG_RELEASE_WAKE_LOCK:
if (mWakeLock != null) {
mWakeLock.release();
mWakeLock = null;
Log.w(TAG, "Release Wake Lock");
}
break;
case SHUTDOWN:
closeService();
break;
case LOAD_CONTACTS:
BluetoothPbapUtils.loadAllContacts(mContext, this);
break;
case CHECK_SECONDARY_VERSION_COUNTER:
BluetoothPbapUtils.updateSecondaryVersionCounter(mContext, this);
break;
case ROLLOVER_COUNTERS:
BluetoothPbapUtils.rolloverCounters();
break;
default:
break;
}
}
};
private void setState(int state) {
setState(state, BluetoothPbap.RESULT_SUCCESS);
}
private synchronized void setState(int state, int result) {
if (state != mState) {
if (DEBUG) Log.d(TAG, "Pbap state " + mState + " -> " + state + ", result = "
+ result);
int prevState = mState;
mState = state;
Intent intent = new Intent(BluetoothPbap.PBAP_STATE_CHANGED_ACTION);
intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
intent.putExtra(BluetoothProfile.EXTRA_STATE, mState);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
sendBroadcast(intent, BLUETOOTH_PERM);
}
}
protected int getState() {
return mState;
}
protected BluetoothDevice getRemoteDevice() {
return mRemoteDevice;
}
private void createPbapNotification(String action) {
NotificationManager nm = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel notificationChannel = new NotificationChannel(PBAP_NOTIFICATION_CHANNEL,
getString(R.string.pbap_notification_group), NotificationManager.IMPORTANCE_HIGH);
nm.createNotificationChannel(notificationChannel);
// Create an intent triggered by clicking on the status icon.
Intent clickIntent = new Intent();
clickIntent.setClass(this, 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(this, BluetoothPbapService.class);
deleteIntent.setAction(AUTH_CANCELLED_ACTION);
String name = getRemoteDeviceName();
if (action.equals(AUTH_CHALL_ACTION)) {
Notification notification =
new Notification.Builder(this, PBAP_NOTIFICATION_CHANNEL)
.setWhen(System.currentTimeMillis())
.setContentTitle(getString(R.string.auth_notif_title))
.setContentText(getString(R.string.auth_notif_message, name))
.setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
.setTicker(getString(R.string.auth_notif_ticker))
.setColor(getResources().getColor(
com.android.internal.R.color.system_notification_accent_color,
this.getTheme()))
.setFlag(Notification.FLAG_AUTO_CANCEL, true)
.setFlag(Notification.FLAG_ONLY_ALERT_ONCE, true)
.setDefaults(Notification.DEFAULT_SOUND)
.setContentIntent(PendingIntent.getActivity(this, 0, clickIntent, 0))
.setDeleteIntent(PendingIntent.getBroadcast(this, 0, deleteIntent, 0))
.build();
nm.notify(NOTIFICATION_ID_AUTH, notification);
}
}
private void removePbapNotification(int id) {
NotificationManager nm = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(id);
}
public static String getLocalPhoneNum() {
return sLocalPhoneNum;
}
public static String getLocalPhoneName() {
return sLocalPhoneName;
}
public static String getRemoteDeviceName() {
return sRemoteDeviceName;
}
@Override
protected IProfileServiceBinder initBinder() {
return new PbapBinder(this);
}
@Override
protected boolean start() {
Log.v(TAG, "start()");
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
filter.addAction(AUTH_RESPONSE_ACTION);
filter.addAction(AUTH_CANCELLED_ACTION);
mInterrupted = false;
BluetoothPbapConfig.init(this);
mSessionStatusHandler.sendMessage(mSessionStatusHandler.obtainMessage(START_LISTENER));
if (mContactChangeObserver == null) {
registerReceiver(mPbapReceiver, filter);
try {
if (DEBUG) Log.d(TAG, "Registering observer");
mContactChangeObserver = new BluetoothPbapContentObserver();
getContentResolver().registerContentObserver(
DevicePolicyUtils.getEnterprisePhoneUri(this), false,
mContactChangeObserver);
} catch (SQLiteException e) {
Log.e(TAG, "SQLite exception: " + e);
} catch (IllegalStateException e) {
Log.e(TAG, "Illegal state exception, content observer is already registered");
}
}
return true;
}
@Override
protected boolean stop() {
Log.v(TAG, "stop()");
if (mContactChangeObserver == null) {
Log.i(TAG, "Avoid unregister when receiver it is not registered");
return true;
}
try {
unregisterReceiver(mPbapReceiver);
getContentResolver().unregisterContentObserver(mContactChangeObserver);
mContactChangeObserver = null;
} catch (Exception e) {
Log.w(TAG, "Unable to unregister pbap receiver", e);
}
mSessionStatusHandler.obtainMessage(SHUTDOWN).sendToTarget();
setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
return true;
}
protected void disconnect() {
synchronized (this) {
if (mState == BluetoothPbap.STATE_CONNECTED) {
if (mServerSession != null) {
mServerSession.close();
mServerSession = null;
}
closeConnectionSocket();
setState(BluetoothPbap.STATE_DISCONNECTED, BluetoothPbap.RESULT_CANCELED);
}
}
}
// Has to be a static class or a memory leak can occur.
private static class PbapBinder extends IBluetoothPbap.Stub implements IProfileServiceBinder {
private BluetoothPbapService mService;
private BluetoothPbapService getService(String perm) {
if (!Utils.checkCaller()) {
Log.w(TAG, "not allowed for non-active user");
return null;
}
if (mService != null && mService.isAvailable()) {
mService.enforceCallingOrSelfPermission(perm, "Need " + perm + " permission");
return mService;
}
return null;
}
PbapBinder(BluetoothPbapService service) {
Log.v(TAG, "PbapBinder()");
mService = service;
}
public boolean cleanup() {
mService = null;
return true;
}
public int getState() {
if (DEBUG) Log.d(TAG, "getState = " + mService.getState());
BluetoothPbapService service = getService(BLUETOOTH_PERM);
if (service == null) return BluetoothPbap.STATE_DISCONNECTED;
return service.getState();
}
public BluetoothDevice getClient() {
if (DEBUG) Log.d(TAG, "getClient = " + mService.getRemoteDevice());
BluetoothPbapService service = getService(BLUETOOTH_PERM);
if (service == null) return null;
return service.getRemoteDevice();
}
public boolean isConnected(BluetoothDevice device) {
if (DEBUG) Log.d(TAG, "isConnected " + device);
BluetoothPbapService service = getService(BLUETOOTH_PERM);
if (service == null) return false;
return service.getState() == BluetoothPbap.STATE_CONNECTED
&& service.getRemoteDevice().equals(device);
}
public boolean connect(BluetoothDevice device) {
BluetoothPbapService service = getService(BLUETOOTH_ADMIN_PERM);
return false;
}
public void disconnect() {
if (DEBUG) Log.d(TAG, "disconnect");
BluetoothPbapService service = getService(BLUETOOTH_ADMIN_PERM);
if (service == null) return;
service.disconnect();
}
}
synchronized private void startSocketListeners() {
if (DEBUG) Log.d(TAG, "startsocketListener");
if (mServerSession != null) {
if (DEBUG) Log.d(TAG, "mServerSession exists - shutting it down...");
mServerSession.close();
mServerSession = null;
}
closeConnectionSocket();
if (mServerSockets != null) {
mServerSockets.prepareForNewConnect();
} else {
mServerSockets = ObexServerSockets.create(this);
if (mServerSockets == null) {
// TODO: Handle - was not handled before
Log.e(TAG, "Failed to start the listeners");
return;
}
SdpManager sdpManager = SdpManager.getDefaultManager();
if (sdpManager == null) {
Log.e(TAG, "Failed to start the listeners sdp null ");
return;
}
if (mAdapter != null && mSdpHandle >= 0) {
Log.d(TAG, "Removing SDP record for PBAP with SDP handle:" + mSdpHandle);
boolean status = sdpManager.removeSdpRecord(mSdpHandle);
Log.d(TAG, "RemoveSDPrecord returns " + status);
mSdpHandle = -1;
}
mSdpHandle = SdpManager.getDefaultManager().createPbapPseRecord(
"OBEX Phonebook Access Server", mServerSockets.getRfcommChannel(),
mServerSockets.getL2capPsm(), SDP_PBAP_SERVER_VERSION,
SDP_PBAP_SUPPORTED_REPOSITORIES, SDP_PBAP_SUPPORTED_FEATURES);
// fetch Pbap Params to check if significant change has happened to Database
BluetoothPbapUtils.fetchPbapParams(mContext);
if (DEBUG) Log.d(TAG, "PBAP server with handle:" + mSdpHandle);
}
}
long getDbIdentifier() {
return BluetoothPbapUtils.mDbIdentifier.get();
}
private void setUserTimeoutAlarm() {
if (DEBUG) Log.d(TAG, "SetUserTimeOutAlarm()");
if (mAlarmManager == null) {
mAlarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
}
mRemoveTimeoutMsg = true;
Intent timeoutIntent = new Intent(USER_CONFIRM_TIMEOUT_ACTION);
PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, timeoutIntent, 0);
mAlarmManager.set(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + USER_CONFIRM_TIMEOUT_VALUE, pIntent);
}
@Override
public boolean onConnect(BluetoothDevice remoteDevice, BluetoothSocket socket) {
mRemoteDevice = remoteDevice;
if (mRemoteDevice == null || socket == null) {
Log.i(TAG, "mRemoteDevice :" + mRemoteDevice + " socket :" + socket);
return false;
}
mConnSocket = socket;
sRemoteDeviceName = mRemoteDevice.getName();
// In case getRemoteName failed and return null
if (TextUtils.isEmpty(sRemoteDeviceName)) {
sRemoteDeviceName = getString(R.string.defaultname);
}
int permission = mRemoteDevice.getPhonebookAccessPermission();
if (DEBUG) Log.d(TAG, "getPhonebookAccessPermission() = " + permission);
if (permission == BluetoothDevice.ACCESS_ALLOWED) {
try {
startObexServerSession();
} catch (IOException ex) {
Log.e(TAG, "Caught exception starting obex server session" + ex.toString());
}
if (!BluetoothPbapUtils.contactsLoaded) {
mSessionStatusHandler.sendMessage(
mSessionStatusHandler.obtainMessage(LOAD_CONTACTS));
}
} else if (permission == BluetoothDevice.ACCESS_REJECTED) {
if (DEBUG) {
Log.d(TAG, "incoming connection rejected from: " + sRemoteDeviceName
+ " automatically as already rejected device");
}
return false;
} else { // permission == BluetoothDevice.ACCESS_UNKNOWN
// Send an Intent to Settings app to ask user preference.
Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
intent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, getPackageName());
mIsWaitingAuthorization = true;
sendOrderedBroadcast(intent, BLUETOOTH_ADMIN_PERM);
if (VERBOSE)
Log.v(TAG, "waiting for authorization for connection from: " + sRemoteDeviceName);
/* In case car kit time out and try to use HFP for phonebook
* access, while UI still there waiting for user to confirm */
mSessionStatusHandler.sendMessageDelayed(
mSessionStatusHandler.obtainMessage(USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE);
/* We will continue the process when we receive
* BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY from Settings app. */
}
return true;
};
/**
* Called when an unrecoverable error occurred in an accept thread.
* Close down the server socket, and restart.
* TODO: Change to message, to call start in correct context.
*/
@Override
public synchronized void onAcceptFailed() {
// Force socket listener to restart
mServerSockets = null;
if (!mInterrupted && mAdapter != null && mAdapter.isEnabled()) {
startSocketListeners();
}
}
}