diff --git a/android/Bluetooth4Facade/AndroidManifest.xml b/android/Bluetooth4Facade/AndroidManifest.xml index 6db1de0cd..0d5006bdf 100644 --- a/android/Bluetooth4Facade/AndroidManifest.xml +++ b/android/Bluetooth4Facade/AndroidManifest.xml @@ -1,6 +1,6 @@ diff --git a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/bluetooth/BluetoothNonpublicApi.java b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/bluetooth/BluetoothNonpublicApi.java index 1d7742723..1ab57335f 100644 --- a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/bluetooth/BluetoothNonpublicApi.java +++ b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/bluetooth/BluetoothNonpublicApi.java @@ -1,5 +1,6 @@ package com.googlecode.android_scripting.bluetooth; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; @@ -13,11 +14,18 @@ */ public class BluetoothNonpublicApi { - // BUG: negative number = under searching... + // adapter + public static final int STATE_UNKNOWN = -1; // temporary + public static final int STATE_BLE_ON = 15; + public static final String ACTION_BLE_STATE_CHANGED = + "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; + + // profile + // BUG: numbers around 65536 = under searching... public static final int PRIORITY_AUTO_CONNECT = 1000; public static final int PRIORITY_ON = 100; public static final int PRIORITY_OFF = 0; - public static final int PRIORITY_UNDEFINED = -65536; + public static final int PRIORITY_UNDEFINED = -1; public static final int MAP = 9; public static final int PAN = 5; @@ -29,6 +37,21 @@ public class BluetoothNonpublicApi { public static final int INPUT_DEVICE = 4; public static final int AVRCP_CONTROLLER = 12; + // Device + public static final String ACTION_CONNECTION_ACCESS_REQUEST = + "android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST"; + public static final String ACTION_CONNECTION_ACCESS_REPLY = + "android.bluetooth.device.action.CONNECTION_ACCESS_REPLY"; + public static final int PAIRING_VARIANT_CONSENT = 3; + + // MapClient + public static final String ACTION_MESSAGE_RECEIVED = + "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED"; + public static final String ACTION_MESSAGE_SENT_SUCCESSFULLY = + "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY"; + public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY = + "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY"; + public static boolean connectProfile(BluetoothProfile prf, BluetoothDevice sink) { Log.e("connect function won't work with no-system app."); if (prf == null) {return false;} @@ -122,4 +145,59 @@ public static boolean setPriorityProfile(BluetoothProfile prf, } return false; } + + public static Integer getLeState(BluetoothAdapter adp + ) { + Log.e("getLeState function won't work with no-system app."); + if (adp == null) {return STATE_UNKNOWN;} + + try { + Method method = adp.getClass().getMethod("getLeState"); + if(method != null) { + return (Integer)method.invoke(adp); + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return STATE_UNKNOWN; + } + + public static void setScanMode(BluetoothAdapter adp, + Integer mode) { + + setScanMode(true, adp, mode, null); + } + + public static void setScanMode(BluetoothAdapter adp, + Integer mode, + Integer duration) { + setScanMode(false, adp, mode, duration); + } + + public static void setScanMode(boolean f2opt, + BluetoothAdapter adp, + Integer mode, + Integer duration) { + Log.e("setScanMode function won't work with no-system app."); + if (adp == null) {return;} + + try { + Method method = adp.getClass().getMethod("setScanMode"); + if(method != null) { + if (f2opt) {method.invoke(adp, mode);} + else {method.invoke(adp, mode, duration);} + return; + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } } diff --git a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/bluetooth/BluetoothPairingHelper.java b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/bluetooth/BluetoothPairingHelper.java index 67de98947..142c67791 100644 --- a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/bluetooth/BluetoothPairingHelper.java +++ b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/bluetooth/BluetoothPairingHelper.java @@ -14,7 +14,7 @@ * the License. */ -package com.googlecode.android_scripting.facade.bluetooth; +package com.googlecode.android_scripting.bluetooth; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; @@ -46,8 +46,10 @@ public void onReceive(Context c, Intent intent) { Log.d("Bluetooth pairing intent received: " + action); BluetoothDevice mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if(action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) { + /* TODO: try to implement. mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED); mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); + */ int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR); Log.d("Processing Action Paring Request with type " + type); int pin = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY,0); @@ -57,7 +59,7 @@ public void onReceive(Context c, Intent intent) { result.putString("DeviceAddress", deviceAddress); mEventFacade.postEvent("BluetoothActionPairingRequest", result.clone()); result.clear(); - if(type == BluetoothDevice.PAIRING_VARIANT_CONSENT) { + if(type == BluetoothNonpublicApi.PAIRING_VARIANT_CONSENT) { mDevice.setPairingConfirmation(true); Log.d("Connection auto-confirmed by consent"); abortBroadcast(); // Abort the broadcast so Settings app doesn't get it. @@ -71,7 +73,8 @@ public void onReceive(Context c, Intent intent) { try { userConfirmEvent = mEventFacade.eventWaitFor( "BluetoothActionPairingRequestUserConfirm", - true, DEFAULT_TIMEOUT_MS); + DEFAULT_TIMEOUT_MS); + // true, DEFAULT_TIMEOUT_MS); } catch (InterruptedException e) { Log.d("Connection interrupted"); userConfirmEvent = null; @@ -93,7 +96,8 @@ public void onReceive(Context c, Intent intent) { abortBroadcast(); // Abort the broadcast so Settings app doesn't get it. } } - else if(action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST)) { + else if(action.equals(BluetoothNonpublicApi.ACTION_CONNECTION_ACCESS_REQUEST)) { + /* TODO: try to implement. int type = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, BluetoothDevice.ERROR); Log.d("Processing Action Connection Access Request type " + type); if(type == BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS || @@ -115,7 +119,8 @@ else if(action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST)) { Log.d("Sending connection access acceptance intent."); abortBroadcast(); c.sendBroadcast(newIntent, android.Manifest.permission.BLUETOOTH_ADMIN); - } + } + */ } } diff --git a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/Bluetooth4Facade.java b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/Bluetooth4Facade.java index 949580515..73b669ffb 100644 --- a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/Bluetooth4Facade.java +++ b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/Bluetooth4Facade.java @@ -32,6 +32,7 @@ import com.googlecode.android_scripting.Constants; import com.googlecode.android_scripting.Log; import com.googlecode.android_scripting.MainThread; +import com.googlecode.android_scripting.bluetooth.BluetoothNonpublicApi; import com.googlecode.android_scripting.facade.EventFacade; import com.googlecode.android_scripting.facade.FacadeManager; import com.googlecode.android_scripting.jsonrpc.RpcReceiver; @@ -162,9 +163,9 @@ class BleStateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (action.equals(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)) { - int state = mBluetoothAdapter.getLeState(); - if (state == BluetoothAdapter.STATE_BLE_ON) { + if (action.equals(BluetoothNonpublicApi.ACTION_BLE_STATE_CHANGED)) { + int state = BluetoothNonpublicApi.getLeState(mBluetoothAdapter); + if (state == BluetoothNonpublicApi.STATE_BLE_ON) { mEventFacade.postEvent("BleStateChangedOn", new Bundle()); mService.unregisterReceiver(mBleStateReceiver); } else if (state == BluetoothAdapter.STATE_OFF) { @@ -177,7 +178,8 @@ public void onReceive(Context context, Intent intent) { public static boolean deviceMatch(BluetoothDevice device, String deviceID) { - return deviceID.equals(device.getAliasName()) || deviceID.equals(device.getAddress()); + // if (deviceID.equals(device.getAliasName())) {return true;} + return deviceID.equals(device.getAddress()); } public static BluetoothDevice getDevice(ConcurrentHashMap devices, String device) @@ -214,8 +216,8 @@ public static boolean deviceExists(Collection devices, String d @Rpc(description = "Requests that the device be made connectable.") public void bluetoothMakeConnectable() { - mBluetoothAdapter - .setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); + BluetoothNonpublicApi.setScanMode(mBluetoothAdapter, + BluetoothAdapter.SCAN_MODE_CONNECTABLE); } @Rpc(description = "Returns active Bluetooth connections.") @@ -327,8 +329,8 @@ public void bluetoothMakeDiscoverable( @RpcDefault("300") Integer duration) { Log.d("Making discoverable for " + duration + " seconds.\n"); - mBluetoothAdapter - .setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, duration); + BluetoothNonpublicApi.setScanMode(mBluetoothAdapter, + BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, duration); if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, duration); @@ -393,7 +395,7 @@ public String bluetoothReadLine( @Rpc(description = "Requests that the device be not discoverable.") public void bluetoothMakeUndiscoverable() { Log.d("Making undiscoverable\n"); - mBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_NONE); + BluetoothNonpublicApi.setScanMode(mBluetoothAdapter, BluetoothAdapter.SCAN_MODE_NONE); } @Rpc(description = "Queries a remote device for it's name or null if it can't be resolved") @@ -457,7 +459,9 @@ public Boolean checkBluetoothState() { @Rpc(description = "Factory reset bluetooth settings.", returns = "True if successful.") public boolean bluetoothFactoryReset() { - return mBluetoothAdapter.factoryReset(); + Log.e("factoryReset won't work in no-system app."); + return false; + // return mBluetoothAdapter.factoryReset(); } @Rpc(description = "Toggle Bluetooth on and off.", returns = "True if Bluetooth is enabled.") @@ -473,7 +477,6 @@ public Boolean toggleBluetoothState( enabled = !checkBluetoothState(); } if (enabled) { - return mBluetoothAdapter.enable(); if (prompt) { Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); // TODO(damonkohler): Use the result to determine if this was successful. At any rate, keep @@ -554,7 +557,10 @@ public boolean bluetoothConfigHciSnoopLog( @RpcParameter(name = "value", description = "enable or disable log") Boolean value ) { - return mBluetoothAdapter.configHciSnoopLog(value); + Log.e("configHciSnoopLog won't work in no-system app."); + return false; + // TODO: try to implement. + // return mBluetoothAdapter.configHciSnoopLog(value); } @Rpc(description = "Get Bluetooth controller activity energy info.") @@ -562,43 +568,53 @@ public String bluetoothGetControllerActivityEnergyInfo( @RpcParameter(name = "value") Integer value ) { + return "Disabled in user app SL4A"; + /* TODO: try to implement. BluetoothActivityEnergyInfo energyInfo = mBluetoothAdapter .getControllerActivityEnergyInfo(value); while (energyInfo == null) { energyInfo = mBluetoothAdapter.getControllerActivityEnergyInfo(value); } return energyInfo.toString(); + */ } @Rpc(description = "Return true if hardware has entries" + "available for matching beacons.") public boolean bluetoothIsHardwareTrackingFiltersAvailable() { - return mBluetoothAdapter.isHardwareTrackingFiltersAvailable(); + Log.e("isHardwareTrackingFiltersAvailable won't in no-system app."); + return false; + // TODO: try to implement. + // return mBluetoothAdapter.isHardwareTrackingFiltersAvailable(); } @Rpc(description = "Gets the current state of LE.") public int bluetoothGetLeState() { - return mBluetoothAdapter.getLeState(); + return BluetoothNonpublicApi.getLeState(mBluetoothAdapter); } @Rpc(description = "Enables BLE functionalities.") public boolean bluetoothEnableBLE() { mService.registerReceiver(mBleStateReceiver, - new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)); - return mBluetoothAdapter.enableBLE(); + new IntentFilter(BluetoothNonpublicApi.ACTION_BLE_STATE_CHANGED)); + Log.e("enableBLE won't work in no-system app."); + return false; + // return mBluetoothAdapter.enableBLE(); } @Rpc(description = "Disables BLE functionalities.") public boolean bluetoothDisableBLE() { mService.registerReceiver(mBleStateReceiver, - new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)); - return mBluetoothAdapter.disableBLE(); + new IntentFilter(BluetoothNonpublicApi.ACTION_BLE_STATE_CHANGED)); + Log.e("disableBLE won't work in no-system app."); + return false; + // return mBluetoothAdapter.disableBLE(); } @Rpc(description = "Listen for a Bluetooth LE State Change.") public boolean bluetoothListenForBleStateChange() { mService.registerReceiver(mBleStateReceiver, - new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)); + new IntentFilter(BluetoothNonpublicApi.ACTION_BLE_STATE_CHANGED)); return true; } diff --git a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothA2dpSinkFacade.java b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothA2dpSinkFacade.java index 527e8413a..4c12fa0d0 100644 --- a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothA2dpSinkFacade.java +++ b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothA2dpSinkFacade.java @@ -96,7 +96,7 @@ public Integer bluetoothA2dpSinkGetPriority( @RpcParameter(name = "device", description = "Mac address of a BT device.") String deviceStr) throws Exception { - if (sA2dpSinkProfile == null) return BluetoothProfile.PRIORITY_UNDEFINED; + if (sA2dpSinkProfile == null) return BluetoothNonpublicApi.PRIORITY_UNDEFINED; BluetoothDevice device = Bluetooth4Facade.getDevice(mBluetoothAdapter.getBondedDevices(), deviceStr); return BluetoothNonpublicApi.getPriorityProfile(sA2dpSinkProfile, device); diff --git a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothConnectionFacade.java b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothConnectionFacade.java index 0174d5071..cd10d993c 100644 --- a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothConnectionFacade.java +++ b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothConnectionFacade.java @@ -48,6 +48,7 @@ import com.googlecode.android_scripting.Log; import com.googlecode.android_scripting.bluetooth.BluetoothNonpublicApi; import com.googlecode.android_scripting.bluetooth.BluetoothUuid; +import com.googlecode.android_scripting.bluetooth.BluetoothPairingHelper; import com.googlecode.android_scripting.facade.Bluetooth4Facade; import com.googlecode.android_scripting.facade.EventFacade; import com.googlecode.android_scripting.facade.FacadeManager; @@ -68,6 +69,7 @@ public class BluetoothConnectionFacade extends RpcReceiver { private final BluetoothAdapter mBluetoothAdapter; private final BluetoothManager mBluetoothManager; // private final BluetoothPairingHelper mPairingHelper; + private final BluetoothPairingHelper mPairingHelper; private final Map listeningDevices; private final EventFacade mEventFacade; @@ -110,7 +112,7 @@ public BluetoothConnectionFacade(FacadeManager manager) { listeningDevices = Collections.synchronizedMap(new HashMap()); mEventFacade = manager.getReceiver(EventFacade.class); - // mPairingHelper = new BluetoothPairingHelper(mEventFacade); + mPairingHelper = new BluetoothPairingHelper(mEventFacade); mA2dpProfile = manager.getReceiver(BluetoothA2dpFacade.class); mA2dpSinkProfile = manager.getReceiver(BluetoothA2dpSinkFacade.class); mHidProfile = manager.getReceiver(BluetoothHidFacade.class); @@ -126,8 +128,8 @@ public BluetoothConnectionFacade(FacadeManager manager) { mDiscoverConnectFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); mPairingFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST); - mPairingFilter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); - mPairingFilter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); + mPairingFilter.addAction(BluetoothNonpublicApi.ACTION_CONNECTION_ACCESS_REQUEST); + mPairingFilter.addAction(BluetoothNonpublicApi.ACTION_CONNECTION_ACCESS_REPLY); mPairingFilter.setPriority(999); mBondFilter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED); @@ -135,7 +137,15 @@ public BluetoothConnectionFacade(FacadeManager manager) { mBondFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); mA2dpStateChangeFilter = new IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); - /* NOTE: can't s + mA2dpSinkStateChangeFilter = null; + mHidStateChangeFilter = null; + mHspStateChangeFilter = null; + mHfpClientStateChangeFilter = null; + mPbapClientStateChangeFilter = null; + mPanStateChangeFilter = null; + mMapClientStateChangeFilter = null; + mMapStateChangeFilter = null; + /* NOTE: can't build with normal SDK. mA2dpSinkStateChangeFilter = new IntentFilter(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED); mHidStateChangeFilter = @@ -505,7 +515,8 @@ public void bluetoothStartPairingHelper( public List bluetoothGetConnectedDevices() { ArrayList results = new ArrayList(); for (BluetoothDevice bd : mBluetoothAdapter.getBondedDevices()) { - if (bd.isConnected()) { + if (true) { + // if (bd.isConnected()) { // TODO: try to implement. results.add(bd); } } @@ -521,7 +532,8 @@ public List bluetoothGetConnectedLeDevices(Integer profile) { public Boolean bluetoothIsDeviceConnected(String deviceID) { for (BluetoothDevice bd : mBluetoothAdapter.getBondedDevices()) { if (Bluetooth4Facade.deviceMatch(bd, deviceID)) { - return bd.isConnected(); + return true; + // return bd.isConnected(); // TODO: try to implement. } } return false; @@ -600,7 +612,9 @@ public Boolean bluetoothUnbond( String deviceID) throws Exception { BluetoothDevice mDevice = Bluetooth4Facade.getDevice(mBluetoothAdapter.getBondedDevices(), deviceID); - return mDevice.removeBond(); + Log.e("removeBond won't work in no-system app."); + return false; + // return mDevice.removeBond(); } @Rpc(description = "Connect to a device that is already bonded.") @@ -657,7 +671,8 @@ public void bluetoothChangeProfileAccessPermission( deviceID); switch (profileID) { case BluetoothNonpublicApi.PBAP: - mDevice.setPhonebookAccessPermission(access); + // mDevice.setPhonebookAccessPermission(access); + Log.e("setPhonebookAccessPermission won't work in no-system app."); break; default: Log.w("Unsupported profile access change."); diff --git a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHfpClientFacade.java b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHfpClientFacade.java index c1aab8645..588d8234a 100644 --- a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHfpClientFacade.java +++ b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHfpClientFacade.java @@ -90,8 +90,10 @@ public void bluetoothHfpClientSetPriority( @RpcParameter(name = "priority", description = "Priority that needs to be set.") Integer priority) throws Exception { - BluetoothNonpublicApi.setPriorityProfile( - sHfpClientProfile, device, priority); + BluetoothDevice device = + Bluetooth4Facade.getDevice(mBluetoothAdapter.getBondedDevices(), deviceStr); + BluetoothNonpublicApi.setPriorityProfile( + sHfpClientProfile, device, priority); } @Rpc(description = "Get priority of the profile") @@ -99,7 +101,7 @@ public Integer bluetoothHfpClientGetPriority( @RpcParameter(name = "device", description = "Mac address of a BT device.") String deviceStr) throws Exception { - if (sHfpClientProfile == null) return BluetoothProfile.PRIORITY_UNDEFINED; + if (sHfpClientProfile == null) return BluetoothNonpublicApi.PRIORITY_UNDEFINED; BluetoothDevice device = Bluetooth4Facade.getDevice(mBluetoothAdapter.getBondedDevices(), deviceStr); return BluetoothNonpublicApi.getPriorityProfile( diff --git a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHidFacade.java b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHidFacade.java index e4ae901e4..ad77827ef 100644 --- a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHidFacade.java +++ b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHidFacade.java @@ -89,7 +89,7 @@ public Boolean bluetoothHidConnect( if (sHidProfile == null) return false; BluetoothDevice mDevice = Bluetooth4Facade.getDevice(Bluetooth4Facade.DiscoveredDevices, device); - Log.d("Connecting to device " + mDevice.getAliasName()); + // Log.d("Connecting to device " + mDevice.getAliasName()); return hidConnect(mDevice); } @@ -143,7 +143,9 @@ public Boolean bluetoothHidSetReport( BluetoothDevice device = Bluetooth4Facade.getDevice(sHidProfile.getConnectedDevices(), deviceID); Log.d("type " + type.getBytes()[0]); - return sHidProfile.setReport(device, type.getBytes()[0], report); + Log.e("setReport won't work in no-system app."); + return false; + // return sHidProfile.setReport(device, type.getBytes()[0], report); } @Rpc(description = "Send Get_Report command to the connected HID input device.") @@ -161,7 +163,9 @@ public Boolean bluetoothHidGetReport( BluetoothDevice device = Bluetooth4Facade.getDevice(sHidProfile.getConnectedDevices(), deviceID); Log.d("type " + type.getBytes()[0] + "reportId " + reportId.getBytes()[0]); - return sHidProfile.getReport(device, type.getBytes()[0], reportId.getBytes()[0], buffSize); + Log.e("getReport won't work in no-system app."); + return false; + // return sHidProfile.getReport(device, type.getBytes()[0], reportId.getBytes()[0], buffSize); } @Rpc(description = "Send data to a connected HID device.") @@ -173,7 +177,10 @@ public Boolean bluetoothHidSendData( String report) throws Exception { BluetoothDevice device = Bluetooth4Facade.getDevice(sHidProfile.getConnectedDevices(), deviceID); - return sHidProfile.sendData(device, report); + Log.e("sendData won't work in no-system app."); + return false; + // TODO: try to implement. + // return sHidProfile.sendData(device, report); } @Rpc(description = "Send virtual unplug to a connected HID device.") @@ -183,7 +190,9 @@ public Boolean bluetoothHidVirtualUnplug( String deviceID) throws Exception { BluetoothDevice device = Bluetooth4Facade.getDevice(sHidProfile.getConnectedDevices(), deviceID); - return sHidProfile.virtualUnplug(device); + Log.e("virtualUnplug won't work in no-system app."); + return false; + // return sHidProfile.virtualUnplug(device); } @Rpc(description = "Test byte transfer.") diff --git a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHspFacade.java b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHspFacade.java index 9dbff2b43..9e05b6c93 100644 --- a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHspFacade.java +++ b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHspFacade.java @@ -68,11 +68,11 @@ public void onServiceDisconnected(int profile) { } public Boolean hspConnect(BluetoothDevice device) { - BluetoothNonpublicApi.connectProfile(sHspProfile, device); + return BluetoothNonpublicApi.connectProfile(sHspProfile, device); } public Boolean hspDisconnect(BluetoothDevice device) { - BluetoothNonpublicApi.disconnectProfile(sHspProfile, device); + return BluetoothNonpublicApi.disconnectProfile(sHspProfile, device); } @Rpc(description = "Is Hsp profile ready.") diff --git a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothLeScanFacade.java b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothLeScanFacade.java index db739a66c..4231fb5f0 100644 --- a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothLeScanFacade.java +++ b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothLeScanFacade.java @@ -34,6 +34,7 @@ import android.bluetooth.le.ScanSettings; import android.os.Bundle; import android.os.ParcelUuid; +import android.os.Parcelable; import com.googlecode.android_scripting.Log; import com.googlecode.android_scripting.MainThread; @@ -355,7 +356,8 @@ public void bleSetScanSettingsScanMode( public void bleSetScanSettingsResultType( @RpcParameter(name = "scanResultType") Integer scanResultType) { - mScanSettingsBuilder.setScanResultType(scanResultType); + Log.e("setScanResultType won't work in no-system app."); + // mScanSettingsBuilder.setScanResultType(scanResultType); } /** * Get ScanSetting's callback type @@ -787,7 +789,9 @@ public void bleSetScanSettingsMatchMode( public int bleGetScanSettingsMatchMode( @RpcParameter(name = "scanSettingsIndex") Integer scanSettingsIndex ) { - return mScanSettingsList.get(scanSettingsIndex).getMatchMode(); + Log.e("getMatchMode won't work in no-system app."); + return 0; + // return mScanSettingsList.get(scanSettingsIndex).getMatchMode(); } @Rpc(description = "Set the scan setting's number of matches") @@ -800,7 +804,9 @@ public void bleSetScanSettingsNumOfMatches( public int bleGetScanSettingsNumberOfMatches( @RpcParameter(name = "scanSettingsIndex") Integer scanSettingsIndex) { - return mScanSettingsList.get(scanSettingsIndex).getNumOfMatches(); + Log.e("getNumOfMatches won't work in no-system app."); + return 0; + // return mScanSettingsList.get(scanSettingsIndex).getNumOfMatches(); } private class myScanCallback extends ScanCallback { @@ -854,7 +860,7 @@ public void onBatchScanResults(List results) { mResults.putLong("Timestamp", System.currentTimeMillis() / 1000); mResults.putInt("ID", index); mResults.putString("Type", "onBatchScanResults"); - mResults.putParcelableList("Results", results); + mResults.putParcelableArray("Results", results.toArray(new ScanResult[0])); mEventFacade.postEvent(mEventType + index + "onBatchScanResult", mResults.clone()); mResults.clear(); } diff --git a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothMapClientFacade.java b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothMapClientFacade.java index 8b83ba5a6..4cbd3d923 100644 --- a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothMapClientFacade.java +++ b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothMapClientFacade.java @@ -93,12 +93,12 @@ public BluetoothMapClientFacade(FacadeManager manager) { mEventFacade = manager.getReceiver(EventFacade.class); mNotificationReceiver = new NotificationReceiver(); - mSendIntent = new Intent(BluetoothMapClient.ACTION_MESSAGE_SENT_SUCCESSFULLY); - mDeliveryIntent = new Intent(BluetoothMapClient.ACTION_MESSAGE_DELIVERED_SUCCESSFULLY); + mSendIntent = new Intent(BluetoothNonpublicApi.ACTION_MESSAGE_SENT_SUCCESSFULLY); + mDeliveryIntent = new Intent(BluetoothNonpublicApi.ACTION_MESSAGE_DELIVERED_SUCCESSFULLY); IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(BluetoothMapClient.ACTION_MESSAGE_RECEIVED); - intentFilter.addAction(BluetoothMapClient.ACTION_MESSAGE_SENT_SUCCESSFULLY); - intentFilter.addAction(BluetoothMapClient.ACTION_MESSAGE_DELIVERED_SUCCESSFULLY); + intentFilter.addAction(BluetoothNonpublicApi.ACTION_MESSAGE_RECEIVED); + intentFilter.addAction(BluetoothNonpublicApi.ACTION_MESSAGE_SENT_SUCCESSFULLY); + intentFilter.addAction(BluetoothNonpublicApi.ACTION_MESSAGE_DELIVERED_SUCCESSFULLY); mService.registerReceiver(mNotificationReceiver, intentFilter); Log.d("notification receiver registered"); } @@ -155,8 +155,10 @@ public Boolean mapSendMessage( Log.d("PhoneNumber count: " + phoneNumbers.length + " = " + phoneNumbers[i]); contacts[i] = Uri.parse(phoneNumbers[i]); } - return sMapProfile.sendMessage(device, contacts, message, mSentIntent, - mDeliveredIntent); + Log.e("sendMessage won't work in no-system app."); + return false; + // return sMapProfile.sendMessage(device, contacts, message, mSentIntent, + // mDeliveredIntent); } catch (Exception e) { Log.d("Error sending message, no such device " + e.toString()); } @@ -213,13 +215,13 @@ public class NotificationReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { Log.d("OnReceive" + intent); String action = intent.getAction(); - if (action.equals(BluetoothMapClient.ACTION_MESSAGE_RECEIVED)) { + if (action.equals(BluetoothNonpublicApi.ACTION_MESSAGE_RECEIVED)) { mEventFacade.postEvent(MAP_EVENT, intent.getStringExtra(android.content.Intent.EXTRA_TEXT)); - } else if (action.equals(BluetoothMapClient.ACTION_MESSAGE_SENT_SUCCESSFULLY)) { + } else if (action.equals(BluetoothNonpublicApi.ACTION_MESSAGE_SENT_SUCCESSFULLY)) { mEventFacade.postEvent(MAP_SMS_SENT_SUCCESS, intent.getStringExtra(android.content.Intent.EXTRA_TEXT)); - } else if (action.equals(BluetoothMapClient.ACTION_MESSAGE_DELIVERED_SUCCESSFULLY)) { + } else if (action.equals(BluetoothNonpublicApi.ACTION_MESSAGE_DELIVERED_SUCCESSFULLY)) { mEventFacade.postEvent(MAP_SMS_DELIVER_SUCCESS, intent.getStringExtra(android.content.Intent.EXTRA_TEXT)); } diff --git a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothMapFacade.java b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothMapFacade.java index 47b5c704b..e39ad500a 100644 --- a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothMapFacade.java +++ b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothMapFacade.java @@ -106,7 +106,9 @@ public List bluetoothMapGetConnectedDevices() { @Rpc(description = "Get the currently connected remote Bluetooth device (PCE).") public BluetoothDevice bluetoothMapGetClient() { if (sMapProfile == null) { return null; } - return sMapProfile.getClient(); + return null; + // TODO: try to implement. + // return sMapProfile.getClient(); } @Override diff --git a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothMediaFacade.java b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothMediaFacade.java new file mode 100644 index 000000000..e9be28538 --- /dev/null +++ b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothMediaFacade.java @@ -0,0 +1,487 @@ +/* + * Copyright (C) 2016 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.googlecode.android_scripting.facade.bluetooth; + +import android.app.Service; +import android.content.Intent; +import android.content.ComponentName; +import android.content.Context; + +import android.media.MediaMetadata; +import android.media.MediaMetadataRetriever; +import android.media.session.MediaSessionManager; +import android.media.session.PlaybackState; +import android.media.browse.MediaBrowser; +import android.media.session.MediaController; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; + +import com.googlecode.android_scripting.facade.EventFacade; +import com.googlecode.android_scripting.facade.FacadeManager; +// import com.googlecode.android_scripting.facade.bluetooth.media.BluetoothSL4AAudioSrcMBS; +import com.googlecode.android_scripting.jsonrpc.RpcReceiver; +import com.googlecode.android_scripting.rpc.Rpc; +import com.googlecode.android_scripting.rpc.RpcParameter; +import com.googlecode.android_scripting.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * SL4A Facade for running Bluetooth Media related test cases + * The APIs provided here can be grouped into 3 categories: + * 1. Those that can run on both an Audio Source and Sink + * 2. Those that makes sense to run only on a Audio Source like a phone + * 3. Those that makes sense to run only on a Audio Sink like a Car. + * + * This media test framework consists of 3 classes: + * 1. BluetoothMediaFacade - this class that provides the APIs that a RPC client can interact with + * 2. BluetoothSL4AMBS - This is a MediaBrowserService that is intended to run on the Audio Source + * (phone). This MediaBrowserService that runs as part of the SL4A app is used to intercept + * Media key events coming in from a AVRCP Controller like Car. Intercepting these events lets us + * instrument the Bluetooth media related tests. + * 3. BluetoothMediaPlayback - The class that the MediaBrowserService uses to play media files. + * It is a UI-less MediaPlayer that serves the purpose of Bluetooth Media testing. + * + * The idea is for the BluetoothMediaFacade to create a BluetoothSL4AMBS MediaSession on the + * Phone (Bluetooth Audio source/Avrcp Target) and use it intercept the Media commands coming + * from the CarKitt (Bluetooth Audio Sink / Avrcp Controller). + * On the Carkitt side, we just create and connect a MediaBrowser to the A2dpMediaBrowserService + * that is part of the Carkitt's Bluetooth Audio App. We use this browser to send media commands + * to the Phone side and intercept the commands with the BluetoothSL4AMBS. + * This set up helps to instrument tests that can test various Bluetooth Media usecases. + */ + +public class BluetoothMediaFacade extends RpcReceiver { + private static final String TAG = "BluetoothMediaFacade"; + private static final boolean VDBG = false; + private final Service mService; + private final Context mContext; + private Handler mHandler; + private MediaSessionManager mSessionManager; + private MediaController mMediaController = null; + private MediaController.Callback mMediaCtrlCallback = null; + private MediaSessionManager.OnActiveSessionsChangedListener mSessionListener; + private MediaBrowser mBrowser = null; + + private static EventFacade mEventFacade; + // Events posted + private static final String EVENT_PLAY_RECEIVED = "playReceived"; + private static final String EVENT_PAUSE_RECEIVED = "pauseReceived"; + private static final String EVENT_SKIP_PREV_RECEIVED = "skipPrevReceived"; + private static final String EVENT_SKIP_NEXT_RECEIVED = "skipNextReceived"; + + // Commands received + private static final String CMD_MEDIA_PLAY = "play"; + private static final String CMD_MEDIA_PAUSE = "pause"; + private static final String CMD_MEDIA_SKIP_NEXT = "skipNext"; + private static final String CMD_MEDIA_SKIP_PREV = "skipPrev"; + + private static final String BLUETOOTH_PKG_NAME = "com.android.bluetooth"; + private static final String BROWSER_SERVICE_NAME = + "com.android.bluetooth.a2dpsink.mbs.A2dpMediaBrowserService"; + private static final String A2DP_MBS_TAG = "A2dpMediaBrowserService"; + + // MediaMetadata keys + private static final String MEDIA_KEY_TITLE = "keyTitle"; + private static final String MEDIA_KEY_ALBUM = "keyAlbum"; + private static final String MEDIA_KEY_ARTIST = "keyArtist"; + private static final String MEDIA_KEY_DURATION = "keyDuration"; + private static final String MEDIA_KEY_NUM_TRACKS = "keyNumTracks"; + + /** + * Following things are initialized here: + * 1. Setup Listeners to Active Media Session changes + * 2. Create a new MediaController.callback instance + */ + public BluetoothMediaFacade(FacadeManager manager) { + super(manager); + mService = manager.getService(); + mEventFacade = manager.getReceiver(EventFacade.class); + mHandler = new Handler(Looper.getMainLooper()); + mContext = mService.getApplicationContext(); + mSessionManager = + (MediaSessionManager) mContext.getSystemService(mContext.MEDIA_SESSION_SERVICE); + mSessionListener = new SessionChangeListener(); + // Listen on Active MediaSession changes, so we can get the active session's MediaController + if (mSessionManager != null) { + ComponentName compName = + new ComponentName(mContext.getPackageName(), this.getClass().getName()); + mSessionManager.addOnActiveSessionsChangedListener(mSessionListener, null, + mHandler); + if (VDBG) { + List mcl = mSessionManager.getActiveSessions(null); + Log.d(TAG + " Num Sessions " + mcl.size()); + for (int i = 0; i < mcl.size(); i++) { + Log.d(TAG + "Active session : " + i + ((MediaController) (mcl.get( + i))).getPackageName() + mcl.get(i)); + // ((MediaController) (mcl.get(i))).getTag()); + } + } + } + mMediaCtrlCallback = new MediaControllerCallback(); + } + + /** + * The listener that was setup for listening to changes to Active Media Sessions. + * This listener is useful in both Car and Phone sides. + */ + private class SessionChangeListener + implements MediaSessionManager.OnActiveSessionsChangedListener { + /** + * On the Phone side, it listens to the BluetoothSL4AAudioSrcMBS (that the SL4A app runs) + * becoming active. + * On the Car side, it listens to the A2dpMediaBrowserService (associated with the + * Bluetooth Audio App) becoming active. + * The idea is to get a handle to the MediaController appropriate for the device, so + * that we can send and receive Media commands. + */ + @Override + public void onActiveSessionsChanged(List controllers) { + if (VDBG) { + Log.d(TAG + " onActiveSessionsChanged : " + controllers.size()); + for (int i = 0; i < controllers.size(); i++) { + Log.d(TAG + "Active session : " + i + ((MediaController) (controllers.get( + i))).getPackageName()); /* + ((MediaController) (controllers.get( + i))).getTag()); */ + } + } + // As explained above, looking for the BluetoothSL4AAudioSrcMBS (when running on Phone) + // or A2dpMediaBrowserService (when running on Carkitt). + for (int i = 0; i < controllers.size(); i++) { + MediaController controller = (MediaController) controllers.get(i); + Log.e("MediaController.getTag won't work in no-system app." + controller); + /* TODO: try to implement. + if ((controller.getTag().contains(BluetoothSL4AAudioSrcMBS.getTag())) + || (controller.getTag().contains(A2DP_MBS_TAG))) { + setCurrentMediaController(controller); + return; + } + */ + } + } + } + + /** + * When the MediaController for the required MediaSession is obtained, register for its + * callbacks. + * Not used yet, but this can be used to verify state changes in both ends. + */ + private class MediaControllerCallback extends MediaController.Callback { + @Override + public void onPlaybackStateChanged(PlaybackState state) { + Log.d(TAG + " onPlaybackStateChanged: " + state.getState()); + } + + @Override + public void onMetadataChanged(MediaMetadata metadata) { + Log.d(TAG + " onMetadataChanged "); + } + } + + /** + * Callback on MediaBrowser.connect() + * This is relevant only on the Carkitt side, since the intent is to connect a MediaBrowser + * to the A2dpMediaBrowser Service that is run by the Car's Bluetooth Audio App. + * On successful connection, we obtain the handle to the corresponding MediaController, + * so we can imitate sending media commands via the Bluetooth Audio App. + */ + MediaBrowser.ConnectionCallback mBrowserConnectionCallback = + new MediaBrowser.ConnectionCallback() { + private static final String classTag = TAG + " BrowserConnectionCallback"; + + @Override + public void onConnected() { + Log.d(classTag + " onConnected: session token " + mBrowser.getSessionToken()); + MediaController mediaController = new MediaController(mContext, + mBrowser.getSessionToken()); + // Update the MediaController + setCurrentMediaController(mediaController); + } + + @Override + public void onConnectionFailed() { + Log.d(classTag + " onConnectionFailed"); + } + }; + + /** + * Update the Current MediaController. + * As has been commented above, we need the MediaController handles to the + * BluetoothSL4AAudioSrcMBS on Phone and A2dpMediaBrowserService on Car to send and receive + * media commands. + * + * @param controller - Controller to update with + */ + private void setCurrentMediaController(MediaController controller) { + Handler mainHandler = new Handler(mContext.getMainLooper()); + if (mMediaController == null && controller != null) { + Log.d(TAG + " Setting MediaController "); // + controller.getTag()); + mMediaController = controller; + mMediaController.registerCallback(mMediaCtrlCallback); + } else if (mMediaController != null && controller != null) { + // We have a new MediaController that we have to update to. + if (controller.getSessionToken().equals(mMediaController.getSessionToken()) + == false) { + Log.d(TAG + " Changing MediaController "); // + controller.getTag()); + mMediaController.unregisterCallback(mMediaCtrlCallback); + mMediaController = controller; + mMediaController.registerCallback(mMediaCtrlCallback, mainHandler); + } + } else if (mMediaController != null && controller == null) { + // Clearing the current MediaController + Log.d(TAG + " Clearing MediaController "); // + mMediaController.getTag()); + mMediaController.unregisterCallback(mMediaCtrlCallback); + mMediaController = controller; + } + } + + /** + * Class method called from {@link BluetoothSL4AAudioSrcMBS} to post an Event through + * EventFacade back to the RPC client. + * This is dispatched from the Phone to the host (RPC Client) to acknowledge that it + * received a playback command. + * + * @param playbackState PlaybackState change that is posted as an Event to the client. + */ + public static void dispatchPlaybackStateChanged(int playbackState) { + Bundle news = new Bundle(); + switch (playbackState) { + case PlaybackState.STATE_PLAYING: + mEventFacade.postEvent(EVENT_PLAY_RECEIVED, news); + break; + case PlaybackState.STATE_PAUSED: + mEventFacade.postEvent(EVENT_PAUSE_RECEIVED, news); + break; + case PlaybackState.STATE_SKIPPING_TO_NEXT: + mEventFacade.postEvent(EVENT_SKIP_NEXT_RECEIVED, news); + break; + case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: + mEventFacade.postEvent(EVENT_SKIP_PREV_RECEIVED, news); + break; + default: + break; + } + } + + /******************************RPC APIS************************************************/ + + /** + * Relevance - Phone and Car. + * Sends the passthrough command through the currently active MediaController. + * If there isn't one, look for the currently active sessions and just pick the first one, + * just a fallback. + * This function is generic enough to be used in either a Phone or the Car side, since + * all this does is to pick the currently active Media Controller and sends a passthrough + * command. In the test setup, this is used to mimic sending a passthrough command from + * Car. + */ + @Rpc(description = "Simulate a passthrough command") + public void bluetoothMediaPassthrough( + @RpcParameter(name = "passthruCmd", description = "play/pause/skipFwd/skipBack") + String passthruCmd) { + Log.d(TAG + "Passthrough Cmd " + passthruCmd); + if (mMediaController == null) { + Log.i(TAG + " Media Controller not ready - Grabbing existing one"); + ComponentName name = + new ComponentName(mContext.getPackageName(), + mSessionListener.getClass().getName()); + List listMC = mSessionManager.getActiveSessions(null); + if (listMC.size() > 0) { + if (VDBG) { + Log.d(TAG + " Num Sessions " + listMC.size()); + for (int i = 0; i < listMC.size(); i++) { + Log.d(TAG + "Active session : " + i + ((MediaController) (listMC.get( + i))).getPackageName()); + // TODO: try to implement: + (MediaController) (listMC.get(i))).getTag()); + } + } + mMediaController = (MediaController) listMC.get(0); + } else { + Log.d(TAG + " No Active Media Session to grab"); + return; + } + } + + switch (passthruCmd) { + case CMD_MEDIA_PLAY: + mMediaController.getTransportControls().play(); + break; + case CMD_MEDIA_PAUSE: + mMediaController.getTransportControls().pause(); + break; + case CMD_MEDIA_SKIP_NEXT: + mMediaController.getTransportControls().skipToNext(); + break; + case CMD_MEDIA_SKIP_PREV: + mMediaController.getTransportControls().skipToPrevious(); + break; + default: + Log.d(TAG + " Unsupported Passthrough Cmd"); + break; + } + } + + /** + * Relevance - Phone and Car. + * Returns the currently playing media's metadata. + * Can be queried on the car and the phone in the middle of a streaming session to + * verify they are in sync. + * + * @return Currently playing Media's metadata + */ + @Rpc(description = "Gets the Metadata of currently playing Media") + public Map bluetoothMediaGetCurrentMediaMetaData() { + Map track = null; + if (mMediaController == null) { + Log.d(TAG + "MediaController Not set"); + return track; + } + MediaMetadata metadata = mMediaController.getMetadata(); + if (metadata == null) { + Log.e("No Metadata available."); + return track; + } + track = new HashMap<>(); + track.put(MEDIA_KEY_TITLE, metadata.getString(MediaMetadata.METADATA_KEY_TITLE)); + track.put(MEDIA_KEY_ALBUM, metadata.getString(MediaMetadata.METADATA_KEY_ALBUM)); + track.put(MEDIA_KEY_ARTIST, metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)); + track.put(MEDIA_KEY_DURATION, + String.valueOf(metadata.getLong(MediaMetadata.METADATA_KEY_DURATION))); + track.put(MEDIA_KEY_NUM_TRACKS, + String.valueOf(metadata.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS))); + return track; + } + + /** + * Relevance - Phone and Car + * Returns the current active media sessions for the device. This is useful to see if a + * Media Session we are interested in is currently active. + * In the Bluetooth Media tests, this is indirectly used to determine if audio is being + * played via BT. For ex., when the Car and Phone are connected via BT and audio is being + * streamed, A2dpMediaBrowserService will be active on the Car side. If the connection is + * terminated in the middle, A2dpMediaBrowserService will no longer be active on the Carkitt, + * whereas BluetoothSL4AAudioSrcMBS will still be active. + * + * @return A list of names of the active media sessions + */ + @Rpc(description = "Get the current active Media Sessions") + public List bluetoothMediaGetActiveMediaSessions() { + List controllers = mSessionManager.getActiveSessions(null); + List sessions = new ArrayList(); + for (MediaController mc : controllers) { + sessions.add(mc.toString()); + // sessions.add(mc.getTag()); + } + return sessions; + } + + /** + * Relevance - Car Only + * Called from the Carkitt to connect a MediaBrowser to the Bluetooth Audio App's + * A2dpMediaBrowserService. The callback on successful connection gives the handle to + * the MediaController through which we can send media commands. + */ + @Rpc(description = "Connect a MediaBrowser to the A2dpMediaBrowserservice in the Carkitt") + public void bluetoothMediaConnectToCarMBS() { + // Create a MediaBrowser to connect to the A2dpMBS + if (mBrowser == null) { + final ComponentName compName = + new ComponentName(BLUETOOTH_PKG_NAME, BROWSER_SERVICE_NAME); + // Note - MediaBrowser connect needs to be done on the Main Thread's handler, + // otherwise we never get the ServiceConnected callback. + Runnable createAndConnectMediaBrowser = new Runnable() { + @Override + public void run() { + mBrowser = new MediaBrowser(mContext, compName, mBrowserConnectionCallback, + null); + if (mBrowser != null) { + Log.d(TAG + " Connecting to MBS"); + mBrowser.connect(); + } else { + Log.d(TAG + " Failed to create a MediaBrowser"); + } + } + }; + + Handler mainHandler = new Handler(mContext.getMainLooper()); + mainHandler.post(createAndConnectMediaBrowser); + } //mBrowser + } + + /** + * Relevance - Phone Only + * Start the BluetoothSL4AAudioSrcMBS on the Phone so the media commands coming in + * via Bluetooth AVRCP can be intercepted by the SL4A test + */ + @Rpc(description = "Start the BluetoothSL4AAudioSrcMBS on Phone.") + public void bluetoothMediaPhoneSL4AMBSStart() { + Log.d(TAG + "Starting BluetoothSL4AAudioSrcMBS"); + // Start the Avrcp Media Browser service. Starting it sets it to active. + /* TODO: try to implement. + Intent startIntent = new Intent(mContext, BluetoothSL4AAudioSrcMBS.class); + mContext.startService(startIntent); + */ + } + + /** + * Relevance - Phone Only + * Stop the BluetoothSL4AAudioSrcMBS + */ + @Rpc(description = "Stop the BluetoothSL4AAudioSrcMBS running on Phone.") + public void bluetoothMediaPhoneSL4AMBSStop() { + Log.d(TAG + "Stopping BluetoothSL4AAudioSrcMBS"); + // Stop the Avrcp Media Browser service. + /* TODO: try to implement. + Intent stopIntent = new Intent(mContext, BluetoothSL4AAudioSrcMBS.class); + mContext.stopService(stopIntent); + */ + } + + /** + * Relevance - Phone only + * This is used to simulate play/pause/skip media commands on the Phone directly, as against + * receiving these commands via AVRCP from the Carkitt. + * This function talks to the BluetoothSL4AAudioSrcMBS to simulate the media command. + * An example test where this would be useful - Play music on Phone that is not connected + * on bluetooth and connect in the middle to verify if music is steamed to the other end. + * + * @param command - Media command to simulate on the Phone + */ + @Rpc(description = "Media Commands on the Phone's BluetoothAvrcpMBS.") + public void bluetoothMediaHandleMediaCommandOnPhone(String command) { + /* TODO: try to implement. + BluetoothSL4AAudioSrcMBS mbs = + BluetoothSL4AAudioSrcMBS.getAvrcpMediaBrowserService(); + if (mbs != null) { + mbs.handleMediaCommand(command); + } else { + Log.e(TAG + " No BluetoothSL4AAudioSrcMBS running on the device"); + } + */ + } + + + @Override + public void shutdown() { + setCurrentMediaController(null); + } +} diff --git a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothPanFacade.java b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothPanFacade.java index 71db0c603..3e9152b8e 100644 --- a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothPanFacade.java +++ b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothPanFacade.java @@ -73,7 +73,8 @@ public void onServiceDisconnected(int profile) { @Rpc(description = "Set Bluetooth Tethering") public void bluetoothPanSetBluetoothTethering( @RpcParameter(name = "enable") Boolean enable) { - sPanProfile.setBluetoothTethering(enable); + Log.e("setBluetoothTegthering wont run in no-system app."); + // sPanProfile.setBluetoothTethering(enable); } public Boolean panConnect(BluetoothDevice device) { @@ -99,7 +100,10 @@ public Boolean bluetoothPanIsTetheringOn() { if (!sIsPanReady || sPanProfile == null) { return false; } - return sPanProfile.isTetheringOn(); + Log.e("isTetheringOn won't work in no-system app."); + return false; + // TODO: try to implement. + // return sPanProfile.isTetheringOn(); } public void shutdown() { diff --git a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/GattClientFacade.java b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/GattClientFacade.java index d99ea8e7a..444cc6af9 100644 --- a/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/GattClientFacade.java +++ b/android/Bluetooth4Facade/src/com/googlecode/android_scripting/facade/bluetooth/GattClientFacade.java @@ -644,7 +644,10 @@ public boolean gattClientReadRSSI( + "remote device") public boolean gattClientRefresh(@RpcParameter(name = "index") Integer index) throws Exception { if (mBluetoothGattList.get(index) != null) { - return mBluetoothGattList.get(index).refresh(); + Log.e("refresh won't work in no-system app."); + return false; + // TODO: try to implement. + // return mBluetoothGattList.get(index).refresh(); } else { throw new Exception("Invalid index input:" + index); } diff --git a/android/ScriptingLayer/build.gradle b/android/ScriptingLayer/build.gradle index 508204d1c..d0a4f89ab 100644 --- a/android/ScriptingLayer/build.gradle +++ b/android/ScriptingLayer/build.gradle @@ -14,6 +14,7 @@ dependencies { compile project(':Common') compile project(':Utils') compile project(':BluetoothFacade') + compile project(':Bluetooth4Facade') compile project(':SignalStrengthFacade') compile project(':TextToSpeechFacade') compile project(':WebCamFacade') diff --git a/android/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java b/android/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java index 8a7e52b92..646c4519a 100644 --- a/android/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java +++ b/android/ScriptingLayer/src/com/googlecode/android_scripting/facade/FacadeConfiguration.java @@ -19,6 +19,7 @@ import com.google.common.collect.Maps; import com.googlecode.android_scripting.Log; +import com.googlecode.android_scripting.facade.bluetooth.BluetoothA2dpSinkFacade; import com.googlecode.android_scripting.facade.ui.UiFacade; import com.googlecode.android_scripting.jsonrpc.RpcReceiver; import com.googlecode.android_scripting.rpc.MethodDescriptor; @@ -122,7 +123,7 @@ public class FacadeConfiguration { sFacadeClassList.add(BluetoothHfpClientFacade.class); sFacadeClassList.add(BluetoothA2dpSinkFacade.class); sFacadeClassList.add(BluetoothPbapClientFacade.class); - sFacadeClassList.add(NsdManagerFacade.class); + // sFacadeClassList.add(NsdManagerFacade.class); sFacadeClassList.add(BluetoothMapClientFacade.class); } diff --git a/android/ScriptingLayerForAndroid/build.gradle b/android/ScriptingLayerForAndroid/build.gradle index 14a8462dc..55d689565 100644 --- a/android/ScriptingLayerForAndroid/build.gradle +++ b/android/ScriptingLayerForAndroid/build.gradle @@ -15,6 +15,7 @@ dependencies { compile project(':Utils') compile project(':QuickAction') compile project(':BluetoothFacade') + compile project(':Bluetooth4Facade') compile project(':SignalStrengthFacade') compile project(':TextToSpeechFacade') compile project(':WebCamFacade') diff --git a/android/ScriptingLayerForAndroid/settings.gradle b/android/ScriptingLayerForAndroid/settings.gradle index 4f81fa3c1..dba78909d 100644 --- a/android/ScriptingLayerForAndroid/settings.gradle +++ b/android/ScriptingLayerForAndroid/settings.gradle @@ -1,5 +1,6 @@ includeFlat 'Common', 'Utils', 'QuickAction' includeFlat 'BluetoothFacade' +includeFlat 'Bluetooth4Facade' includeFlat 'SignalStrengthFacade' includeFlat 'TextToSpeechFacade' includeFlat 'WebCamFacade'