/* * Copyright (c) 2013, The Linux Foundation. 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 Linux Foundation 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, FITNESS FOR A PARTICULAR PURPOSE AND * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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 org.codeaurora.bluetooth.dun; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDun; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetoothDun; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.Message; import android.util.Log; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import android.bluetooth.BluetoothAdapter; import android.os.SystemProperties; import android.app.Service; import android.bluetooth.IBluetooth; import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothSocket; import android.net.LocalSocket; import android.net.LocalSocketAddress; import java.io.OutputStream; import java.io.InputStream; import android.os.IBinder; import java.nio.ByteBuffer; import java.io.IOException; import android.content.ServiceConnection; import android.os.ParcelUuid; import java.util.UUID; import android.text.TextUtils; import android.content.ComponentName; import android.os.RemoteException; import org.codeaurora.bluetooth.R; /** * Provides Bluetooth Dun profile, as a service in the BluetoothExt APK. * @hide */ public class BluetoothDunService extends Service { private static final String TAG = "BluetoothDunService"; public static final String LOG_TAG = "BluetoothDun"; public static boolean VERBOSE; private final static String DUN_SERVER = "qcom.dun.server"; private static final int MESSAGE_START_LISTENER = 0x01; private static final int MESSAGE_DUN_USER_TIMEOUT = 0x02; private static final int USER_CONFIRM_TIMEOUT_VALUE = 30000; // 30 seconds private static final int MON_THREAD_SLEEP_INTERVAL = 200; // 200 mseconds /** * IPC message types which can be exchanged between * Dun server process and BluetoothDunService */ private static final byte DUN_IPC_MSG_DUN_REQUEST = 0x00; private static final byte DUN_IPC_MSG_DUN_RESPONSE = 0x01; private static final byte DUN_IPC_MSG_CTRL_REQUEST = 0x02; private static final byte DUN_IPC_MSG_CTRL_RESPONSE = 0x03; private static final byte DUN_IPC_MSG_MDM_STATUS = 0x04; /** * Control message types between Dun server and * BluetoothDunService */ private static final byte DUN_CRTL_MSG_DISCONNECT_REQ = 0x00; private static final byte DUN_CRTL_MSG_CONNECTED_RESP = 0x01; private static final byte DUN_CRTL_MSG_DISCONNECTED_RESP = 0x02; /** * Offsets of message data with in the IPC message */ private static final byte DUN_IPC_MSG_OFF_MSG_TYPE = 0x00; private static final byte DUN_IPC_MSG_OFF_MSG_LEN = 0x01; private static final byte DUN_IPC_MSG_OFF_MSG = 0x03; /** * Server supported DUN Profile's maximum message size */ private static final int DUN_MAX_MSG_LEN = 32764; /** * IPC message's header size */ private static final int DUN_IPC_HEADER_SIZE = 0x03; /** * IPC message's maximum message size (Dun server <---> DUN service) */ private static final int DUN_MAX_IPC_MSG_LEN = DUN_MAX_MSG_LEN + DUN_IPC_HEADER_SIZE; /** * IPC message's control message size */ private static final byte DUN_IPC_CTRL_MSG_SIZE = 0x01; /** * IPC message's modem status message size */ private static final byte DUN_IPC_MDM_STATUS_MSG_SIZE = 0x01; /** * BT socket options */ private static final int BTSOCK_OPT_GET_MODEM_BITS = 0x01; private static final int BTSOCK_OPT_SET_MODEM_BITS = 0x02; private static final int BTSOCK_OPT_CLR_MODEM_BITS = 0x03; private static IBluetooth mAdapterService = null; private static BluetoothDevice mRemoteDevice = null; private static volatile SocketAcceptThread mAcceptThread = null; /** * Thread for reading the requests from DUN client using the socket * mRfcommSocket and upload the same requests to DUNd using the * socket mDundSocket */ private static volatile UplinkThread mUplinkThread = null; /** * Thtread for reading(download) the responses from DUNd using * the socket mDundSocket and route the same responses to * DUN client using the socket mRfcommSocket */ private static volatile DownlinkThread mDownlinkThread = null; /** * Thtread for monitoring the modem status and base on the status * chnage it will update the DUN server with the updated status */ private static volatile MonitorThread mMonitorThread = null; /** * Listening Rfcomm Socket */ private static volatile BluetoothServerSocket mListenSocket = null; /** * Connected Rfcomm socket */ private static volatile BluetoothSocket mRfcommSocket = null; /** * Socket represents the connection between DUNd and DUN service */ private static volatile LocalSocket mDundSocket = null; private boolean mIsWaitingAuthorization = false; private volatile boolean mInterrupted = false; private boolean mDunEnable = false; private byte mRmtMdmStatus = 0x00; /** * DUN profile's UUID */ private static final String DUN_UUID = "00001103-0000-1000-8000-00805F9B34FB"; /** * Intent indicating incoming connection request which is sent to * BluetoothDunPermissionActivity */ public static final String DUN_ACCESS_REQUEST_ACTION = "org.codeaurora.bluetooth.dun.accessrequest"; /** * Intent indicating incoming connection request accepted by user which is * sent from BluetoothDunPermissionActivity */ public static final String DUN_ACCESS_ALLOWED_ACTION = "org.codeaurora.bluetooth.dun.accessallowed"; /** * Intent indicating incoming connection request denied by user which is * sent from BluetoothDunPermissionActivity */ public static final String DUN_ACCESS_DISALLOWED_ACTION = "org.codeaurora.bluetooth.dun.accessdisallowed"; /** * Intent indicating timeout for user confirmation, which is sent to * BluetoothDunPermissionActivity */ public static final String USER_CONFIRM_TIMEOUT_ACTION = "org.codeaurora.bluetooth.dun.userconfirmtimeout"; public static final String THIS_PACKAGE_NAME = "org.codeaurora.bluetooth"; /** * Intent Extra name indicating always allowed which is sent from * BluetoothDunPermissionActivity */ public static final String DUN_EXTRA_ALWAYS_ALLOWED = "org.codeaurora.bluetooth.dun.alwaysallowed"; // Ensure not conflict with Opp notification ID private static final int DUN_NOTIFICATION_ID_ACCESS = -1000006; /** * Intent Extra name indicating BluetoothDevice which is sent to * BluetoothDunPermissionActivity */ public static final String EXTRA_BLUETOOTH_DEVICE = "org.codeaurora.bluetooth.dun.bluetoothdevice"; /** * String signifies the DUN profile status */ public static final String BLUETOOTH_DUN_PROFILE_STATUS = "bluetooth.dun.status"; public static final ParcelUuid DUN = ParcelUuid.fromString(DUN_UUID); private static final Object mAcceptLock = new Object(); private static final Object mUplinkLock = new Object(); private static final Object mDownlinkLock = new Object(); private static final Object mMonitorLock = new Object(); private static final Object mAuthLock = new Object(); private HashMap mDunDevices; private IBinder mDunBinder; private BluetoothAdapter mAdapter; /** * package and class name to which we send intent to check DUN profile * access permission */ private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings"; private static final String ACCESS_AUTHORITY_CLASS = "com.android.settings.bluetooth.BluetoothPermissionRequest"; private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; @Override public void onCreate() { super.onCreate(); VERBOSE = Log.isLoggable(LOG_TAG, Log.VERBOSE) ? true : false; if (VERBOSE) Log.v(TAG, "Dun Service onCreate"); mDunDevices = new HashMap(); mAdapter = BluetoothAdapter.getDefaultAdapter(); mDunBinder = initBinder(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { mInterrupted = false; if (mAdapter == null) { Log.w(TAG, "Stopping BluetoothDunService: " + "device does not have BT or device is not ready"); // Release all resources closeDunService(); } else { if (intent != null) { parseIntent(intent); } } return START_NOT_STICKY; } @Override public void onDestroy() { if (VERBOSE) Log.v(TAG, "Dun Service onDestroy"); super.onDestroy(); if(mDunDevices != null) mDunDevices.clear(); closeDunService(); if(mDunHandler != null) { mDunHandler.removeCallbacksAndMessages(null); } } @Override public IBinder onBind(Intent intent) { if (VERBOSE) Log.v(TAG, "Dun Service onBind"); return mDunBinder; } private IBinder initBinder() { return new BluetoothDunBinder(this); } private final Handler mDunHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_START_LISTENER: if (mAdapter.isEnabled()) { startRfcommListenerThread(); } break; case MESSAGE_DUN_USER_TIMEOUT: { synchronized(mAuthLock) { if (!mIsWaitingAuthorization) { // already handled, ignore it break; } mIsWaitingAuthorization = false; } Intent intent = new Intent(USER_CONFIRM_TIMEOUT_ACTION); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice); sendBroadcast(intent); removeDunNotification(DUN_NOTIFICATION_ID_ACCESS); /* close the rfcomm socket and restart the listener thread */ closeRfcommSocket(); startRfcommListenerThread(); if (VERBOSE) Log.v(TAG, "DUN user Authorization Timeout"); } break; } } }; // process the intent from DUN receiver private void parseIntent(final Intent intent) { String action = intent.getStringExtra("action"); Log.d(TAG, "parseIntent: action: " + action); int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); if (VERBOSE) Log.v(TAG, "parseIntent: state: " + state); boolean removeTimeoutMsg = true; if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { if ((state == BluetoothAdapter.STATE_ON) && (!mDunEnable)) { synchronized(mConnection) { try { if (mAdapterService == null) { Intent bindIntent = new Intent(IBluetooth.class.getName()); ComponentName comp = bindIntent.resolveSystemService(getPackageManager(), 0); bindIntent.setComponent(comp); if (comp == null || !bindServiceAsUser(bindIntent, mConnection, 0, android.os.Process.myUserHandle())) { Log.e(TAG, "Could not bind to Bluetooth Dun Service with " + bindIntent); return; } } } catch (Exception e) { Log.e(TAG, "bindService Exception", e); return; } } Log.v(TAG, "Starting DUN server process"); try { SystemProperties.set(BLUETOOTH_DUN_PROFILE_STATUS, "running"); mDunEnable = true; } catch (RuntimeException e) { Log.v(TAG, "Could not start DUN server process: " + e); } if (mDunEnable) { mDunHandler.sendMessage(mDunHandler .obtainMessage(MESSAGE_START_LISTENER)); } else { //DUN server process is not started successfully. //So clean up service connection to avoid service connection leak if (mAdapterService != null) { try { mAdapterService = null; unbindService(mConnection); } catch (IllegalArgumentException e) { Log.e(TAG, "could not unbind the adapter Service", e); } } return; } } else if (state == BluetoothAdapter.STATE_TURNING_OFF) { // Send any pending timeout now, as this service will be destroyed. if (mDunHandler.hasMessages(MESSAGE_DUN_USER_TIMEOUT)) { Intent timeoutIntent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); timeoutIntent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS); sendBroadcast(timeoutIntent, BLUETOOTH_ADMIN_PERM); } if (mDunEnable) { Log.v(TAG, "Stopping DUN server process"); try { SystemProperties.set(BLUETOOTH_DUN_PROFILE_STATUS, "stopped"); } catch (RuntimeException e) { Log.v(TAG, "Could not stop DUN server process: " + e); } synchronized(mConnection) { try { mAdapterService = null; unbindService(mConnection); } catch (Exception e) { Log.e(TAG, "", e); } } // Release all resources closeDunService(); mDunEnable = false; } synchronized(mAuthLock) { if (mIsWaitingAuthorization) { removeDunNotification(DUN_NOTIFICATION_ID_ACCESS); mIsWaitingAuthorization = false; } } } else { removeTimeoutMsg = false; } } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) && mIsWaitingAuthorization) { if (mRemoteDevice == null) { Log.e(TAG, "Unexpected error!"); return; } if (mDunHandler != null) { /* Let the user timeout handle this case as well */ mDunHandler.sendMessage(mDunHandler.obtainMessage(MESSAGE_DUN_USER_TIMEOUT)); removeTimeoutMsg = false; } } else if (action.equals(DUN_ACCESS_ALLOWED_ACTION)) { if (VERBOSE) Log.v(TAG, "DunService-Received ACCESS_ALLOWED_ACTION"); if (mRemoteDevice == null) { Log.e(TAG, "Unexpected error!"); return; } synchronized(mAuthLock) { if (!mIsWaitingAuthorization) { // this reply is not for us return; } mIsWaitingAuthorization = false; } if (intent.getBooleanExtra(BluetoothDunService.DUN_EXTRA_ALWAYS_ALLOWED, false) == true) { if(mRemoteDevice != null) { mRemoteDevice.setTrust(true); Log.v(TAG, "setTrust() TRUE " + mRemoteDevice.getName()); } } /* start the uplink thread */ startUplinkThread(); } else if (action.equals(DUN_ACCESS_DISALLOWED_ACTION)) { /* close the rfcomm socket and restart the listener thread */ Log.d(TAG,"DUN_ACCESS_DISALLOWED_ACTION:" + mIsWaitingAuthorization); mIsWaitingAuthorization = false; closeRfcommSocket(); startRfcommListenerThread(); } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) { if (intent.hasExtra(BluetoothDevice.EXTRA_DEVICE)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if(device != null) Log.d(TAG,"device: "+ device.getName()); if(mRemoteDevice != null) Log.d(TAG," Remtedevie: "+mRemoteDevice.getName()); if ((device != null) && (mRemoteDevice != null) && (device.equals(mRemoteDevice)) && (mRemoteDevice.getTrustState()) && (intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE) == BluetoothDevice.BOND_NONE)) { Log.d(TAG,"BOND_STATE_CHANGED REFRESH trustDevices"+ device.getName()); mRemoteDevice.setTrust(false); } } } else { removeTimeoutMsg = false; } if (removeTimeoutMsg && mDunHandler != null) { mDunHandler.removeMessages(MESSAGE_DUN_USER_TIMEOUT); } } private void startRfcommListenerThread() { if (VERBOSE) Log.v(TAG, "DUN Service startRfcommListenerThread"); synchronized(mAcceptLock) { // if it is already running,close it if (mAcceptThread != null) { try { mAcceptThread.shutdown(); mAcceptThread.join(); mAcceptThread = null; } catch (InterruptedException ex) { Log.w(TAG, "mAcceptThread close error" + ex); } } if (mAcceptThread == null) { mAcceptThread = new SocketAcceptThread(); mAcceptThread.setName("BluetoothDunAcceptThread"); mAcceptThread.start(); } } } private void startUplinkThread() { if (VERBOSE) Log.v(TAG, "DUN Service startUplinkThread"); synchronized(mUplinkLock) { if (mUplinkThread != null) { try { mUplinkThread.shutdown(); mUplinkThread.join(); mUplinkThread = null; } catch (InterruptedException ex) { Log.w(TAG, "mUplinkThread close error" + ex); } } if (mUplinkThread == null) { mUplinkThread = new UplinkThread(); mUplinkThread.setName("BluetoothDunUplinkThread"); mUplinkThread.start(); } } } private void startDownlinkThread() { if (VERBOSE) Log.v(TAG, "DUN Service startDownlinkThread"); synchronized(mDownlinkLock) { if (mDownlinkThread == null) { mDownlinkThread = new DownlinkThread(); mDownlinkThread.setName("BluetoothDunDownlinkThread"); mDownlinkThread.start(); } } } private void startMonitorThread() { if (VERBOSE) Log.v(TAG, "DUN Service startMonitorThread"); synchronized(mMonitorLock) { if (mMonitorThread == null) { mMonitorThread = new MonitorThread(); mMonitorThread .setName("BluetoothDunMonitorThread"); mMonitorThread .start(); } } } private final boolean initRfcommSocket() { if (VERBOSE) Log.v(TAG, "Dun Service initRfcommSocket"); boolean initRfcommSocketOK = 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++) { initRfcommSocketOK = true; try { // start listening for incoming rfcomm channel connection mListenSocket = mAdapter.listenUsingRfcommWithServiceRecord("Dial up Networking", DUN.getUuid()); } catch (IOException e) { Log.e(TAG, "Error create RfcommListenSocket " + e.toString()); initRfcommSocketOK = false; } if (!initRfcommSocketOK) { // 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, "initRfcommSocket 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) { initRfcommSocketOK = false; //close the listen socket to avoid resource leakage closeListenSocket(); } if (initRfcommSocketOK) { 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 initRfcommSocketOK; } private final boolean initDundClientSocket() { if (VERBOSE) Log.v(TAG, "DUN initDundClientSocket"); boolean initDundSocketOK = false; try { LocalSocketAddress locSockAddr = new LocalSocketAddress(DUN_SERVER); mDundSocket = new LocalSocket(); mDundSocket.connect(locSockAddr); initDundSocketOK = true; } catch (java.io.IOException e) { Log.e(TAG, "cant connect: " + e); } if (initDundSocketOK) { if (VERBOSE) Log.v(TAG, "Succeed to create Dund socket "); } else { Log.e(TAG, "Error to create Dund socket"); } return initDundSocketOK; } private final synchronized void closeListenSocket() { // exit SocketAcceptThread early Log.d(TAG, "Close listen Socket : "); if (mListenSocket != null) { try { // this will cause mListenSocket.accept() return early with // IOException mListenSocket.close(); mListenSocket = null; } catch (IOException ex) { Log.e(TAG, "Close listen Socket error: " + ex); } } } private final synchronized void closeRfcommSocket() { Log.d(TAG, "Close rfcomm Socket : "); if (mRfcommSocket != null) { try { mRfcommSocket.close(); mRfcommSocket = null; } catch (IOException e) { Log.e(TAG, "Close Rfcomm Socket error: " + e.toString()); } } } private final synchronized void closeDundSocket() { Log.d(TAG, "Close dund Socket : "); if (mDundSocket != null) { try { mDundSocket.close(); mDundSocket = null; } catch (IOException e) { Log.e(TAG, "Close Dund Socket error: " + e.toString()); } } } private final void closeDunService() { Log.d(TAG, "Dun Service closeDunService in"); // exit initRfcommSocket early mInterrupted = true; closeListenSocket(); closeRfcommSocket(); closeDundSocket(); synchronized(mMonitorLock) { if (mMonitorThread != null) { try { mMonitorThread.shutdown(); mMonitorThread.join(); mMonitorThread = null; } catch (InterruptedException ex) { Log.w(TAG, "mMonitorThread close error" + ex); } } } synchronized(mDownlinkLock) { if (mDownlinkThread != null) { try { mDownlinkThread.shutdown(); mDownlinkThread.join(); mDownlinkThread = null; } catch (InterruptedException ex) { Log.w(TAG, "mDownlinkThread close error" + ex); } } } synchronized(mUplinkLock) { if (mUplinkThread != null) { try { mUplinkThread.shutdown(); mUplinkThread.join(); mUplinkThread = null; } catch (InterruptedException ex) { Log.w(TAG, "mUplinkThread close error" + ex); } } } /* remove the MESSAGE_START_LISTENER message which might be posted * by the uplink thread */ mDunHandler.removeMessages(MESSAGE_START_LISTENER); synchronized(mAcceptLock) { if (mAcceptThread != null) { try { mAcceptThread.shutdown(); mAcceptThread.join(); mAcceptThread = null; } catch (InterruptedException ex) { Log.w(TAG, "mAcceptThread close error" + ex); } } } stopSelf(); Log.d(TAG, "Dun Service closeDunService out"); } private void createDunNotification(BluetoothDevice device) { if (VERBOSE) Log.v(TAG, "Creating DUN access notification"); NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); // Create an intent triggered by clicking on the status icon. Intent clickIntent = new Intent(); clickIntent.setClass(this, BluetoothDunPermissionActivity.class); clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); clickIntent.setAction(DUN_ACCESS_REQUEST_ACTION); clickIntent.putExtra(EXTRA_BLUETOOTH_DEVICE, device); // Create an intent triggered by clicking on the // "Clear All Notifications" button Intent deleteIntent = new Intent(); deleteIntent.setClass(this, BluetoothDunReceiver.class); Notification notification = null; String name = device.getName(); if (TextUtils.isEmpty(name)) { name = getString(R.string.defaultname); } deleteIntent.setAction(DUN_ACCESS_DISALLOWED_ACTION); notification = new Notification(android.R.drawable.stat_sys_data_bluetooth, getString(R.string.notif_ticker), System.currentTimeMillis()); notification.setLatestEventInfo(this, getString(R.string.notif_ticker), getString(R.string.notif_message, name), PendingIntent .getActivity(this, 0, clickIntent, 0)); notification.flags |= Notification.FLAG_AUTO_CANCEL; notification.flags |= Notification.FLAG_ONLY_ALERT_ONCE; notification.defaults = Notification.DEFAULT_SOUND; notification.deleteIntent = PendingIntent.getBroadcast(this, 0, deleteIntent, 0); nm.notify(DUN_NOTIFICATION_ID_ACCESS, notification); if (VERBOSE) Log.v(TAG, "Awaiting Authorization : DUN Connection : " + device.getName()); } private void removeDunNotification(int id) { Context context = getApplicationContext(); NotificationManager nm = (NotificationManager) context .getSystemService(Context.NOTIFICATION_SERVICE); nm.cancel(id); } /** * 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 listenSocket; if (mListenSocket == null) { if (!initRfcommSocket()) { return; } } while (!stopped) { try { listenSocket = mListenSocket; if (listenSocket == null) { Log.w(TAG, "mListenSocket is null"); break; } Log.d(TAG, "Listening for rfcomm socket connection..."); mRfcommSocket = mListenSocket.accept(); synchronized (BluetoothDunService.this) { if (mRfcommSocket == null) { Log.w(TAG, " mRfcommSocket is null"); break; } mRemoteDevice = mRfcommSocket.getRemoteDevice(); } if (mRemoteDevice == null) { /* close the rfcomm socket */ closeRfcommSocket(); Log.i(TAG, "getRemoteDevice() = null"); break; } boolean trust = false; if (mRemoteDevice != null) trust = mRemoteDevice.getTrustState(); if (VERBOSE) Log.v(TAG, "GetTrustState() = " + trust); if (trust) { /* start the uplink thread */ startUplinkThread(); } else { createDunNotification(mRemoteDevice); if (VERBOSE) Log.v(TAG, "incoming connection accepted from: "+ mRemoteDevice); mIsWaitingAuthorization = true; mDunHandler.sendMessageDelayed(mDunHandler.obtainMessage(MESSAGE_DUN_USER_TIMEOUT), USER_CONFIRM_TIMEOUT_VALUE); } stopped = true; // job done ,close this thread; if (VERBOSE) Log.v(TAG, "SocketAcceptThread stopped "); } catch (IOException ex) { stopped=true; Log.w(TAG, "Handled Accept thread exception: " + ex.toString()); } } } void shutdown() { stopped = true; interrupt(); } } /** * A thread that runs in the background waiting for remote DUNd socket * connection.Once the connection is established, this thread starts the * downlink thread and starts forwarding the DUN profile requests to * the DUNd on receiving the requests from rfcomm channel. */ private class UplinkThread extends Thread { private boolean stopped = false; private boolean IntExit = false; private OutputStream mDundOutputStream = null; private InputStream mRfcommInputStream = null; ByteBuffer IpcMsgBuffer = ByteBuffer.allocate(DUN_MAX_IPC_MSG_LEN); private int NumRead = 0; private byte TempByte = 0; @Override public void run() { if (mDundSocket == null) { if (!initDundClientSocket()) { /* close the rfcomm socket to avoid resource leakage */ closeRfcommSocket(); /*restart the listener thread */ mDunHandler.sendMessage(mDunHandler.obtainMessage(MESSAGE_START_LISTENER)); return; } } if (mDundSocket != null && mDundSocket.isConnected()) { try { mDundOutputStream = mDundSocket.getOutputStream(); } catch (IOException ex) { Log.w(TAG, "Handled mDundOutputStream exception: " + ex.toString()); } } else { Log.w(TAG, "UplinkThread: Dund Socket is not connected: " + mDundSocket); return; } if (mRfcommSocket != null && mRfcommSocket.isConnected()) { try { mRfcommInputStream = mRfcommSocket.getInputStream(); } catch (IOException ex) { Log.w(TAG, "Handled mRfcommInputStream exception: " + ex.toString()); } } else { Log.w(TAG, "UplinkThread: rfcomm Socket is not connected: " + mRfcommSocket); return; } // create the downlink thread for reading the responses //from DUN server process startDownlinkThread(); // create the modem status monitor thread startMonitorThread(); while (!stopped) { try { if (VERBOSE) Log.v(TAG, "Reading the DUN requests from Rfcomm channel"); /* Read the DUN request from Rfcomm channel */ NumRead = mRfcommInputStream.read(IpcMsgBuffer.array(), DUN_IPC_MSG_OFF_MSG, DUN_MAX_MSG_LEN); if (NumRead < 0) { IntExit = true; break; } else if (NumRead != 0) { /* Write the same DUN request to the DUN server socket with some additional parameters */ IpcMsgBuffer.put(DUN_IPC_MSG_OFF_MSG_TYPE, DUN_IPC_MSG_DUN_REQUEST); IpcMsgBuffer.putShort(DUN_IPC_MSG_OFF_MSG_LEN, (short)NumRead); /* swap bytes of message len as buffer is Big Endian */ TempByte = IpcMsgBuffer.get(DUN_IPC_MSG_OFF_MSG_LEN); IpcMsgBuffer.put( DUN_IPC_MSG_OFF_MSG_LEN, IpcMsgBuffer.get(DUN_IPC_MSG_OFF_MSG_LEN + 1)); IpcMsgBuffer.put(DUN_IPC_MSG_OFF_MSG_LEN + 1, TempByte); try { mDundOutputStream.write(IpcMsgBuffer.array(), 0, NumRead + DUN_IPC_HEADER_SIZE); } catch (IOException ex) { break; } } } catch (IOException ex) { IntExit = true; Log.w(TAG, "Handled Rfcomm channel Read exception: " + ex.toString()); break; } } // send the disconneciton request immediate to Dun server if (IntExit) { disconnect(mRemoteDevice); } /* close the dund socket */ closeDundSocket(); /* close the rfcomm socket */ closeRfcommSocket(); // Intimate the Settings APP about the disconnection handleDunDeviceStateChange(mRemoteDevice, BluetoothProfile.STATE_DISCONNECTED); /* wait for dun downlink thread to close */ if (VERBOSE) Log.v(TAG, "wait for dun downlink thread to close"); synchronized (mDownlinkLock) { if (mDownlinkThread != null) { try { mDownlinkThread.shutdown(); mDownlinkThread.join(); mDownlinkThread = null; } catch (InterruptedException ex) { Log.w(TAG, "mDownlinkThread close error" + ex); } } } /* wait for dun monitor thread to close */ if (VERBOSE) Log.v(TAG, "wait for dun monitor thread to close "); synchronized (mMonitorLock) { if (mMonitorThread != null) { try { mMonitorThread.shutdown(); mMonitorThread.join(); mMonitorThread = null; } catch (InterruptedException ex) { Log.w(TAG, "mMonitorThread close error" + ex); } } } if (IntExit && !stopped) { if (VERBOSE) Log.v(TAG, "starting the listener thread "); /* start the listener thread */ mDunHandler.sendMessage(mDunHandler.obtainMessage(MESSAGE_START_LISTENER)); } /* reset the modem status */ mRmtMdmStatus = 0x00; Log.d(TAG, "uplink thread exited"); } void shutdown() { stopped = true; interrupt(); } } /** * A thread that runs in the background and monitors the modem status of rfcomm * channel and when there is a change in the modem status it will update the * DUN server process with the new status. */ private class MonitorThread extends Thread { private boolean stopped = false; private int len = 0; private byte modemStatus = 0; ByteBuffer IpcMsgBuffer = ByteBuffer.allocate(DUN_IPC_MDM_STATUS_MSG_SIZE); private byte TempByte = 0; byte mdmBits = 0; @Override public void run() { while (!stopped) { try { //wait for 200 ms Thread.sleep(MON_THREAD_SLEEP_INTERVAL); } catch (InterruptedException e) { Log.e(TAG, "MonitorThread thread was interrupted"); break; } try { if ((mRfcommSocket == null) || (!mRfcommSocket.isConnected())) { Log.w(TAG, "MonitorThread: rfcomm Socket is not connected: " + mRfcommSocket); break; } len = mRfcommSocket.getSocketOpt(BTSOCK_OPT_GET_MODEM_BITS, IpcMsgBuffer.array()); if(len != DUN_IPC_MDM_STATUS_MSG_SIZE) { Log.w(TAG, "getSocketOpt return mismatch len: " + len); continue; } } catch (IOException ex) { Log.w(TAG, "Handled getSocketOpt Exception: " + ex.toString()); } if( mdmBits != IpcMsgBuffer.get(0)) { mdmBits = IpcMsgBuffer.get(0); notifyModemStatus(mdmBits); } } Log.d(TAG, "MonitorThread thread exited"); } void shutdown() { stopped = true; interrupt(); } } /** * A thread that runs in the background and starts forwarding the * DUN profile responses received from Dund to the rfcomm channel. */ private class DownlinkThread extends Thread { private boolean stopped = false; private OutputStream mRfcommOutputStream = null; private InputStream mDundInputStream = null; ByteBuffer IpcMsgBuffer = ByteBuffer.allocate(2*DUN_MAX_IPC_MSG_LEN); private int NumRead = 0; private int ReadIndex = 0; private short MsgLen = 0; private byte TempByte = 0; @Override public void run() { if (mDundSocket != null && mDundSocket.isConnected()) { try { mDundInputStream = mDundSocket.getInputStream(); } catch (IOException ex) { Log.w(TAG, "Handled mDundInputStream exception: " + ex.toString()); } } else { Log.w(TAG, "DownlinkThread: Dund Socket is not connected: " + mDundSocket); return; } if (mRfcommSocket != null && mRfcommSocket.isConnected()) { try { mRfcommOutputStream = mRfcommSocket.getOutputStream(); } catch (IOException ex) { Log.w(TAG, "Handled mRfcommOutputStream exception: " + ex.toString()); } } else { Log.w(TAG, "DownlinkThread: rfcomm Socket is not connected: " + mRfcommSocket); return; } while (!stopped) { try { /* Read the DUN responses from DUN server */ NumRead = mDundInputStream.read(IpcMsgBuffer.array(),0, DUN_MAX_IPC_MSG_LEN); Log.d(TAG, "Read the DUN response from Dund, NumRead: " + NumRead); if (NumRead < 0) { break; } else if(NumRead != 0) { /* some times read buffer contains multiple responses */ do { if (VERBOSE) Log.v(TAG, "ReadIndex: " + ReadIndex); /* swap bytes of message len as buffer is Big Endian */ TempByte = IpcMsgBuffer.get(ReadIndex + DUN_IPC_MSG_OFF_MSG_LEN); IpcMsgBuffer.put(ReadIndex + DUN_IPC_MSG_OFF_MSG_LEN, IpcMsgBuffer.get(ReadIndex + DUN_IPC_MSG_OFF_MSG_LEN + 1)); IpcMsgBuffer.put(ReadIndex + DUN_IPC_MSG_OFF_MSG_LEN + 1, TempByte); MsgLen = IpcMsgBuffer.getShort(ReadIndex + DUN_IPC_MSG_OFF_MSG_LEN); /* Max DUN msg/response length including IPC header is 32767, which can only be read from the DunD socket in single read. As msg/response length is varied & single msg/response can be smaller than max length, So multiple msgs/responses are read in single read. Boundary condition hits, when max length is already read, which include multiple responses, but some of the bytes of last response still need to be read. */ if((ReadIndex + MsgLen + DUN_IPC_HEADER_SIZE) > NumRead) { int tempNumRead = 0; tempNumRead = mDundInputStream.read(IpcMsgBuffer.array(), NumRead, ReadIndex + MsgLen + DUN_IPC_HEADER_SIZE - NumRead); Log.w(TAG, "Boundary condition hit, ReadIndex: " + ReadIndex + " MsgLen: " + MsgLen + " NumRead: " + NumRead + " tempNumRead: " + tempNumRead); if (tempNumRead >= 0) NumRead += tempNumRead; else break; } if (IpcMsgBuffer.get(ReadIndex + DUN_IPC_MSG_OFF_MSG_TYPE) == DUN_IPC_MSG_DUN_RESPONSE) { try { mRfcommOutputStream.write(IpcMsgBuffer.array(), ReadIndex + DUN_IPC_MSG_OFF_MSG, IpcMsgBuffer.getShort(ReadIndex + DUN_IPC_MSG_OFF_MSG_LEN)); } catch (IOException ex) { stopped = true; break; } if (VERBOSE) Log.v(TAG, "DownlinkThread Msg written to Rfcomm"); } else if (IpcMsgBuffer.get(ReadIndex + DUN_IPC_MSG_OFF_MSG_TYPE) == DUN_IPC_MSG_CTRL_RESPONSE) { if (IpcMsgBuffer.get(ReadIndex + DUN_IPC_MSG_OFF_MSG) == DUN_CRTL_MSG_CONNECTED_RESP) { handleDunDeviceStateChange(mRemoteDevice, BluetoothProfile.STATE_CONNECTED); } else if (IpcMsgBuffer.get(ReadIndex + DUN_IPC_MSG_OFF_MSG) == DUN_CRTL_MSG_DISCONNECTED_RESP) { handleDunDeviceStateChange(mRemoteDevice, BluetoothProfile.STATE_DISCONNECTED); } if (VERBOSE) Log.v(TAG, "DownlinkThread control message received"); } else if (IpcMsgBuffer.get(ReadIndex + DUN_IPC_MSG_OFF_MSG_TYPE) == DUN_IPC_MSG_MDM_STATUS) { /* handle the modem status messages */ handleModemStatusChange(IpcMsgBuffer.get(ReadIndex + DUN_IPC_MSG_OFF_MSG)); if (VERBOSE) Log.v(TAG, "DownlinkThread modem status message received"); } ReadIndex = ReadIndex + MsgLen + DUN_IPC_HEADER_SIZE; } while(ReadIndex > 0 && ReadIndex < NumRead); } } catch (IOException ex) { stopped = true; Log.w(TAG, "Handled Dund Read exception: " + ex.toString()); } /* reset the ReadIndex */ ReadIndex = 0; } /* close the dund socket */ closeDundSocket(); /* close the rfcomm socket */ closeRfcommSocket(); Log.d(TAG, "downlink thread exited "); } void shutdown() { stopped = true; interrupt(); } } boolean disconnect(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); OutputStream mDundOutputStream = null; int WriteLen = DUN_IPC_HEADER_SIZE + DUN_IPC_CTRL_MSG_SIZE; ByteBuffer IpcMsgBuffer = ByteBuffer.allocate(WriteLen); if (mDundSocket != null && mDundSocket.isConnected()) { try { mDundOutputStream = mDundSocket.getOutputStream(); } catch (IOException ex) { Log.w(TAG, "disconnect: Handled mDundOutputStream exception: " + ex.toString()); } } else { Log.w(TAG, "disconnect: Dund Socket is not connected: " + mDundSocket); return false; } IpcMsgBuffer.put(DUN_IPC_MSG_OFF_MSG_TYPE, DUN_IPC_MSG_CTRL_REQUEST); IpcMsgBuffer.putShort(DUN_IPC_MSG_OFF_MSG_LEN,DUN_IPC_CTRL_MSG_SIZE); IpcMsgBuffer.put(DUN_IPC_MSG_OFF_MSG, DUN_CRTL_MSG_DISCONNECT_REQ); try { if (mDundOutputStream != null) mDundOutputStream.write(IpcMsgBuffer.array(), 0, WriteLen); } catch (IOException ex) { Log.w(TAG, "disconnect: Handled mDundOutputStream write exception: " + ex.toString()); } return true; } boolean notifyModemStatus(byte status) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); OutputStream mDundOutputStream = null; int WriteLen = DUN_IPC_HEADER_SIZE + DUN_IPC_MDM_STATUS_MSG_SIZE; ByteBuffer IpcMsgBuffer = ByteBuffer.allocate(WriteLen); Log.d(TAG, "notifyModemStatus: status: " + status); if (mDundSocket != null && mDundSocket.isConnected()) { try { mDundOutputStream = mDundSocket.getOutputStream(); } catch (IOException ex) { Log.w(TAG, "notifyModemStatus: mDundOutputStream exception: " + ex.toString()); } } else { Log.w(TAG, "notifyModemStatus: Dund Socket is not connected: " + mDundSocket); return false; } IpcMsgBuffer.put(DUN_IPC_MSG_OFF_MSG_TYPE, DUN_IPC_MSG_MDM_STATUS); IpcMsgBuffer.putShort(DUN_IPC_MSG_OFF_MSG_LEN,DUN_IPC_MDM_STATUS_MSG_SIZE); IpcMsgBuffer.put(DUN_IPC_MSG_OFF_MSG, status); try { if (mDundOutputStream != null) mDundOutputStream.write(IpcMsgBuffer.array(), 0, WriteLen); } catch (IOException ex) { Log.e(TAG, "Handled mDundOutputStream write exception: " + ex.toString()); } return true; } int getConnectionState(BluetoothDevice device) { BluetoothDunDevice dunDevice = mDunDevices.get(device); if (dunDevice == null) { return BluetoothDun.STATE_DISCONNECTED; } return dunDevice.mState; } public void notifyProfileConnectionStateChanged(BluetoothDevice device, int profileId, int newState, int prevState) { if (mAdapterService != null) { try { mAdapterService.sendConnectionStateChange(device, profileId, newState, prevState); }catch (RemoteException re) { Log.e(TAG, "",re); } } } private final void handleDunDeviceStateChange(BluetoothDevice device, int state) { int prevState; BluetoothDunDevice dunDevice = mDunDevices.get(device); if (dunDevice == null) { prevState = BluetoothProfile.STATE_DISCONNECTED; } else { prevState = dunDevice.mState; } Log.d(TAG, "handleDunDeviceStateChange device: " + device + " preState: " + prevState + " state: " + state); if (prevState == state) return; if (dunDevice == null) { dunDevice = new BluetoothDunDevice(state); mDunDevices.put(device, dunDevice); } else { dunDevice.mState = state; } /* Notifying the connection state change of the profile before sending the intent for connection state change, as it was causing a race condition, with the UI not being updated with the correct connection state. */ notifyProfileConnectionStateChanged(device, BluetoothProfile.DUN, state, prevState); Intent intent = new Intent(BluetoothDun.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothDun.EXTRA_PREVIOUS_STATE, prevState); intent.putExtra(BluetoothDun.EXTRA_STATE, state); sendBroadcast(intent, BLUETOOTH_PERM); } private final void handleModemStatusChange(byte status) { byte mdmBits = 0; int WriteLen = DUN_IPC_MDM_STATUS_MSG_SIZE; ByteBuffer IpcMsgBuffer = ByteBuffer.allocate(WriteLen); if (VERBOSE) Log.v(TAG, "handleModemStatusChange status: " + status); if ((mRfcommSocket == null) || (!mRfcommSocket.isConnected())) { Log.w(TAG, "rfcomm socket is not connected" + mRfcommSocket); return; } if(mRmtMdmStatus != status) { mdmBits = (byte)((~(int)mRmtMdmStatus) & ((int)mRmtMdmStatus ^ (int)status)); if (VERBOSE) Log.v(TAG, "handleModemStatusChange mdmBits " + mdmBits); if(mdmBits > 0) { IpcMsgBuffer.put(0, mdmBits); try { mRfcommSocket.setSocketOpt(BTSOCK_OPT_SET_MODEM_BITS, IpcMsgBuffer.array(), WriteLen); } catch (IOException ex) { Log.w(TAG, "Handled getSocketOpt Exception: " + ex.toString()); } } mdmBits = (byte)(((int)mRmtMdmStatus) & ((int)mRmtMdmStatus ^ (int)status)); if (VERBOSE) Log.v(TAG, "handleModemStatusChange mdmBits " + mdmBits); if(mdmBits > 0) { IpcMsgBuffer.put(0, mdmBits); try { mRfcommSocket.setSocketOpt(BTSOCK_OPT_CLR_MODEM_BITS, IpcMsgBuffer.array(), WriteLen); } catch (IOException ex) { Log.w(TAG, "Handled getSocketOpt Exception: " + ex.toString()); } } mRmtMdmStatus = status; } } List getConnectedDevices() { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); List devices = getDevicesMatchingConnectionStates( new int[] {BluetoothProfile.STATE_CONNECTED}); return devices; } List getDevicesMatchingConnectionStates(int[] states) { enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); List dunDevices = new ArrayList(); for (BluetoothDevice device: mDunDevices.keySet()) { int dunDeviceState = getConnectionState(device); for (int state : states) { if (state == dunDeviceState) { dunDevices.add(device); break; } } } return dunDevices; } private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { mAdapterService = IBluetooth.Stub.asInterface(service); } public void onServiceDisconnected(ComponentName className) { mAdapterService = null; } }; /** * Handlers for incoming service calls */ private static class BluetoothDunBinder extends IBluetoothDun.Stub { private BluetoothDunService mService; public BluetoothDunBinder(BluetoothDunService svc) { mService = svc; } private BluetoothDunService getService() { if (mService != null) return mService; return null; } public boolean disconnect(BluetoothDevice device) { BluetoothDunService service = getService(); if (service == null) return false; return service.disconnect(device); } public int getConnectionState(BluetoothDevice device) { BluetoothDunService service = getService(); if (service == null) return BluetoothDun.STATE_DISCONNECTED; return service.getConnectionState(device); } public List getConnectedDevices() { BluetoothDunService service = getService(); if (service == null) return new ArrayList(0); return service.getConnectedDevices(); } public List getDevicesMatchingConnectionStates(int[] states) { BluetoothDunService service = getService(); if (service == null) return new ArrayList(0); return service.getDevicesMatchingConnectionStates(states); } }; private class BluetoothDunDevice { private int mState; BluetoothDunDevice(int state) { mState = state; } } }