aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-07 01:05:30 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-07 01:05:30 +0000
commita0b5c5974cba9d228bd6aff6f0afab45c25f28b9 (patch)
treee1306c4f7c9f066598917260b8bec88e5c102a77
parent4ee21038bca9e7d9599c08c1e64e545189b54cf5 (diff)
parente72d0b88c6b4dacf2789f39b418e95829a8f9145 (diff)
downloadsl4a-a0b5c5974cba9d228bd6aff6f0afab45c25f28b9.tar.gz
Change-Id: I212428646539ce28a90ad924a5b40f41d6d373e4
-rw-r--r--Common/Android.bp4
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/DataUsageController.java12
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/SettingsFacade.java1
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothConnectionFacade.java56
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHidDeviceFacade.java65
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHidFacade.java5
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothSocketConnFacade.java15
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/bluetooth/GattClientFacade.java15
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/telephony/AudioFileInfo.java41
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/telephony/InCallServiceImpl.java188
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/telephony/PlayAudioInCall.java186
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/telephony/RecordVoiceInCall.java281
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/telephony/SmsFacade.java2
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyConstants.java6
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyManagerFacade.java168
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/uwb/UwbManagerFacade.java103
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/wifi/WifiAwareManagerFacade.java2
-rwxr-xr-xCommon/src/com/googlecode/android_scripting/facade/wifi/WifiManagerFacade.java2
-rw-r--r--Common/src/com/googlecode/android_scripting/facade/wifi/WifiScannerFacade.java5
-rw-r--r--Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java2
-rw-r--r--OWNERS1
-rw-r--r--ScriptingLayerForAndroid/AndroidManifest.xml1
22 files changed, 1126 insertions, 35 deletions
diff --git a/Common/Android.bp b/Common/Android.bp
index 16f03310..41154b29 100644
--- a/Common/Android.bp
+++ b/Common/Android.bp
@@ -34,10 +34,12 @@ java_library {
"junit",
"modules-utils-build",
"aosp_test_rcs_client_base",
+ "com.uwb.support.ccc",
"com.uwb.support.fira",
],
sdk_version: "core_platform",
+ plugins: ["auto_value_plugin"],
libs: [
"framework-wifi.impl", // allow SL4A to access @hide Wifi APIs
"framework-connectivity.impl",
@@ -45,7 +47,7 @@ java_library {
"framework-uwb.impl",
"framework-bluetooth.impl",
"framework",
-
+ "auto_value_annotations",
"telephony-common",
"ims-common",
"bouncycastle-repackaged-unbundled",
diff --git a/Common/src/com/googlecode/android_scripting/facade/DataUsageController.java b/Common/src/com/googlecode/android_scripting/facade/DataUsageController.java
index 6ec89ed1..3e9aa05f 100644
--- a/Common/src/com/googlecode/android_scripting/facade/DataUsageController.java
+++ b/Common/src/com/googlecode/android_scripting/facade/DataUsageController.java
@@ -16,8 +16,11 @@
package com.googlecode.android_scripting.facade;
+import static android.app.usage.NetworkStats.Bucket.METERED_YES;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkTemplate.MATCH_MOBILE;
+import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.telephony.TelephonyManager.SIM_STATE_READY;
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
@@ -39,6 +42,7 @@ import android.util.Pair;
import java.time.ZonedDateTime;
import java.util.Locale;
+import java.util.Set;
/**
* DataUsageController.
@@ -87,7 +91,8 @@ public class DataUsageController {
if (subscriberId == null) {
return warn("no subscriber id");
}
- NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriberId);
+ NetworkTemplate template = new NetworkTemplate.Builder(MATCH_MOBILE)
+ .setMeteredness(METERED_YES).setSubscriberIds(Set.of(subscriberId)).build();
template = NetworkTemplate.normalize(template, mTelephonyManager.getMergedSubscriberIds());
return getDataUsageInfo(template);
@@ -101,7 +106,8 @@ public class DataUsageController {
if (subscriberId == null) {
return warn("no subscriber id");
}
- NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriberId);
+ NetworkTemplate template = new NetworkTemplate.Builder(MATCH_MOBILE)
+ .setMeteredness(METERED_YES).setSubscriberIds(Set.of(subscriberId)).build();
template = NetworkTemplate.normalize(template, mTelephonyManager.getMergedSubscriberIds());
return getDataUsageInfo(template, uId);
@@ -112,7 +118,7 @@ public class DataUsageController {
* @return DataUsageInfo: The Wifi data usage information.
*/
public DataUsageInfo getWifiDataUsageInfo() {
- NetworkTemplate template = NetworkTemplate.buildTemplateWifiWildcard();
+ NetworkTemplate template = new NetworkTemplate.Builder(MATCH_WIFI).build();
return getDataUsageInfo(template);
}
diff --git a/Common/src/com/googlecode/android_scripting/facade/SettingsFacade.java b/Common/src/com/googlecode/android_scripting/facade/SettingsFacade.java
index 0a7223ca..a7b514d2 100644
--- a/Common/src/com/googlecode/android_scripting/facade/SettingsFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/SettingsFacade.java
@@ -269,7 +269,6 @@ public class SettingsFacade extends RpcReceiver {
String previousPassword) {
// mLockPatternUtils.setLockPatternEnabled(true, UserHandle.myUserId());
mLockPatternUtils.setLockScreenDisabled(false, UserHandle.myUserId());
- mLockPatternUtils.setCredentialRequiredToDecrypt(true);
mLockPatternUtils.setLockCredential(LockscreenCredential.createPassword(password),
LockscreenCredential.createPasswordOrNone(previousPassword),
UserHandle.myUserId());
diff --git a/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothConnectionFacade.java b/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothConnectionFacade.java
index 60e97971..33a667e3 100644
--- a/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothConnectionFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothConnectionFacade.java
@@ -576,11 +576,15 @@ public class BluetoothConnectionFacade extends RpcReceiver {
@Rpc(description = "Bluetooth init Bond by Mac Address")
public boolean bluetoothBond(@RpcParameter(name = "macAddress") String macAddress) {
+ mContext.registerReceiver(new BondBroadcastReceiver(),
+ new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
return mBluetoothAdapter.getRemoteDevice(macAddress).createBond();
}
@Rpc(description = "Bluetooth init LE Bond by Mac Address")
public boolean bluetoothLeBond(@RpcParameter(name = "macAddress") String macAddress) {
+ mContext.registerReceiver(new BondBroadcastReceiver(),
+ new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
return mBluetoothAdapter.getRemoteDevice(macAddress).createBond(BluetoothDevice.TRANSPORT_LE);
}
@@ -631,27 +635,41 @@ public class BluetoothConnectionFacade extends RpcReceiver {
}
/**
- * Bond to a device using Out of Band Data.
+ * Bond to a device using Out of Band Data over LE transport. Note that there is a distinction
+ * between the address with type supplied in the oob data and the address and type of the
+ * BluetoothDevice object.
*
- * @param address String representation of address like "00:11:22:33:44:55"
+ * @param oobDataAddress is the MAC address to be used in the oob data
+ * @param oobDataAddressType is the BluetoothDevice.AddressType for the oob data MAC address
* @param transport String "1", "2", "3" to match TRANSPORT_*
* @param c Hex String of the 16 octet confirmation
* @param r Hex String of the 16 octet randomizer
+ * @param address String representation of MAC address for the BluetoothDevice object
+ * @param addressType the BluetoothDevice.AddressType for the BluetoothDevice object
*/
- @Rpc(description = "Creates and Out of Band bond.")
- public void bluetoothCreateBondOutOfBand(@RpcParameter(name = "address") String address,
- @RpcParameter(name = "transport") String transport,
- @RpcParameter(name = "c") String c, @RpcParameter(name = "r") String r) {
- Log.d("bluetoothCreateBondOutOfBand(" + address + ", " + transport + "," + c + ", "
+ @Rpc(description = "Creates and Out of Band LE bond.")
+ public boolean bluetoothCreateLeBondOutOfBand(
+ @RpcParameter(name = "oobDataAddress") String oobDataAddress,
+ @RpcParameter(name = "oobDataAddressType") Integer oobDataAddressType,
+ @RpcParameter(name = "c") String c, @RpcParameter(name = "r") String r,
+ @RpcParameter(name = "address") String address,
+ @RpcParameter(name = "addressType") @RpcDefault("1") Integer addressType) {
+ Log.d("bluetoothCreateLeBondOutOfBand(" + address + ", " + addressType + "," + c + ", "
+ r + ")");
- BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(address);
+ BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteLeDevice(address, addressType);
byte[] addressBytes = new byte[7];
int i = 0;
- for (String s : address.split(":")) {
+ for (String s : oobDataAddress.split(":")) {
addressBytes[i] = hexStringToByteArray(s)[0];
i++;
}
- addressBytes[i] = 0x01;
+
+ // Inserts the oob address type if one is provided
+ if (oobDataAddressType == BluetoothDevice.ADDRESS_TYPE_PUBLIC
+ || oobDataAddressType == BluetoothDevice.ADDRESS_TYPE_RANDOM) {
+ addressBytes[i] = oobDataAddressType.byteValue();
+ }
+
OobData p192 = null;
OobData p256 = new OobData.LeBuilder(hexStringToByteArray(c),
addressBytes, OobData.LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL)
@@ -659,7 +677,7 @@ public class BluetoothConnectionFacade extends RpcReceiver {
.build();
mContext.registerReceiver(new BondBroadcastReceiver(),
new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
- remoteDevice.createBondOutOfBand(Integer.parseInt(transport), p192, p256);
+ return remoteDevice.createBondOutOfBand(BluetoothDevice.TRANSPORT_LE, p192, p256);
}
private class BondBroadcastReceiver extends BroadcastReceiver {
@@ -755,11 +773,17 @@ public class BluetoothConnectionFacade extends RpcReceiver {
@RpcParameter(name = "deviceID",
description = "Name or MAC address of a bluetooth device.")
String deviceID) throws Exception {
- BluetoothDevice mDevice = BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(),
- deviceID);
- mContext.registerReceiver(new BondBroadcastReceiver(),
- new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
- return mDevice.removeBond();
+ // We don't want to crash the test if the test passes an address that cannot be found.
+ try {
+ BluetoothDevice mDevice = BluetoothFacade.getDevice(
+ mBluetoothAdapter.getBondedDevices(), deviceID);
+ mContext.registerReceiver(new BondBroadcastReceiver(),
+ new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
+ return mDevice.removeBond();
+ } catch (Exception e) {
+ Log.d("Failed to find the device by deviceId");
+ return false;
+ }
}
@Rpc(description = "Connect to a device that is already bonded.")
diff --git a/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHidDeviceFacade.java b/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHidDeviceFacade.java
index ba61ded3..bc73e6a2 100644
--- a/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHidDeviceFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHidDeviceFacade.java
@@ -151,9 +151,9 @@ public class BluetoothHidDeviceFacade extends RpcReceiver {
// HID mouse movement
private static final byte[] RIGHT = {0, 1, 0, 0};
- private static final byte[] DOWN = {0, 0, -1, 0};
+ private static final byte[] DOWN = {0, 0, 1, 0};
private static final byte[] LEFT = {0, -1, 0, 0};
- private static final byte[] UP = {0, 0, 1, 0};
+ private static final byte[] UP = {0, 0, -1, 0};
// Default values.
private static final int QOS_TOKEN_RATE = 800; // 9 bytes * 1000000 us / 11250 us
@@ -430,6 +430,34 @@ public class BluetoothHidDeviceFacade extends RpcReceiver {
}
/**
+ * Send a bytes array data report to a connected HID host using interrupt channel.
+ * @param deviceID name or MAC address or the HID input host
+ * @param id report Id, as defined in descriptor. Can be 0 in case Report Id are not defined in
+ * descriptor.
+ * @param report byte array to be sent into HID device
+ * @return true if successfully sent the report; otherwise false
+ * @throws Exception error from Bluetooth HidDevService
+ */
+ @Rpc(description = "Send bytes array report to a connected HID host using interrupt channel.")
+ public Boolean bluetoothHidDeviceSendBytesArrayReport(
+ @RpcParameter(name = "deviceID",
+ description = "Name or MAC address of a bluetooth device.")
+ String deviceID,
+ @RpcParameter(name = "descriptor",
+ description = "Descriptor of the report")
+ Integer id,
+ @RpcParameter(name = "report")
+ byte[] report) throws Exception {
+ if (sHidDeviceProfile == null) {
+ return false;
+ }
+
+ BluetoothDevice device = BluetoothFacade.getDevice(sHidDeviceProfile.getConnectedDevices(),
+ deviceID);
+ return sHidDeviceProfile.sendReport(device, id, report);
+ }
+
+ /**
* Send a report to the connected HID host as reply for GET_REPORT request from the HID host.
* @param deviceID name or MAC address or the HID input host
* @param type type of the report, as in request
@@ -463,6 +491,39 @@ public class BluetoothHidDeviceFacade extends RpcReceiver {
}
/**
+ * Send a bytes array report to the connected HID host as reply for GET_REPORT request
+ * from the HID host.
+ * @param deviceID name or MAC address or the HID input host
+ * @param type type of the report, as in request
+ * @param id id of the report, as in request
+ * @param report byte array to be sent into HID device
+ * @return true if successfully sent the reply report; otherwise false
+ * @throws Exception error from Bluetooth HidDevService
+ */
+ @Rpc(description = "Send reply bytes array report to a connected HID..")
+ public Boolean bluetoothHidDeviceReplyBytesArrayReport(
+ @RpcParameter(name = "deviceID",
+ description = "Name or MAC address of a bluetooth device.")
+ String deviceID,
+ @RpcParameter(name = "type",
+ description = "Type as in the report.")
+ Integer type,
+ @RpcParameter(name = "id",
+ description = "id as in the report.")
+ Integer id,
+ @RpcParameter(name = "report")
+ byte[] report) throws Exception {
+ if (sHidDeviceProfile == null) {
+ return false;
+ }
+
+ BluetoothDevice device = BluetoothFacade.getDevice(sHidDeviceProfile.getConnectedDevices(),
+ deviceID);
+ return sHidDeviceProfile.replyReport(
+ device, (byte) (int) type, (byte) (int) id, report);
+ }
+
+ /**
* Send error handshake message as reply for invalid SET_REPORT request from the HID host.
* @param deviceID name or MAC address or the HID input host
* @param error error byte
diff --git a/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHidFacade.java b/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHidFacade.java
index 24f0b087..6829c90c 100644
--- a/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHidFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothHidFacade.java
@@ -38,6 +38,7 @@ import com.googlecode.android_scripting.rpc.Rpc;
import com.googlecode.android_scripting.rpc.RpcDefault;
import com.googlecode.android_scripting.rpc.RpcParameter;
+import java.util.Arrays;
import java.util.List;
/*
@@ -122,9 +123,9 @@ public class BluetoothHidFacade extends RpcReceiver {
}
break;
case BluetoothHidHost.ACTION_REPORT: {
- char[] report = intent.getCharArrayExtra(
+ byte[] report = intent.getByteArrayExtra(
BluetoothHidHost.EXTRA_REPORT);
- Log.d("Received report: " + String.valueOf(report));
+ Log.d("Received report: " + Arrays.toString(report));
}
break;
case BluetoothHidHost.ACTION_VIRTUAL_UNPLUG_STATUS: {
diff --git a/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothSocketConnFacade.java b/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothSocketConnFacade.java
index 18ca8588..49efe6cf 100644
--- a/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothSocketConnFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/bluetooth/BluetoothSocketConnFacade.java
@@ -21,6 +21,7 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
+import android.os.Bundle;
import com.googlecode.android_scripting.Log;
import com.googlecode.android_scripting.facade.EventFacade;
@@ -55,6 +56,9 @@ public class BluetoothSocketConnFacade extends RpcReceiver {
private AcceptThread mAcceptThread;
private byte mTxPktIndex = 0;
+ private final Bundle mGoodNews;
+ private final Bundle mBadNews;
+
private static final String DEFAULT_PSM = "161"; //=0x00A1
// UUID for SL4A.
@@ -68,6 +72,11 @@ public class BluetoothSocketConnFacade extends RpcReceiver {
mEventFacade = manager.getReceiver(EventFacade.class);
mService = manager.getService();
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ mGoodNews = new Bundle();
+ mGoodNews.putBoolean("Status", true);
+ mBadNews = new Bundle();
+ mBadNews.putBoolean("Status", false);
}
private BluetoothConnection getConnection(String connID) throws IOException {
@@ -731,9 +740,15 @@ public class BluetoothSocketConnFacade extends RpcReceiver {
mConnUuid = addConnection(conn);
Log.d("ConnectThread:run: isConnected=" + mSocket.isConnected() + ", address="
+ mSocket.getRemoteDevice().getAddress() + ", uuid=" + mConnUuid);
+ if (mSocket.isConnected()) {
+ mEventFacade.postEvent("BluetoothSocketConnectSuccess", mGoodNews);
+ } else {
+ mEventFacade.postEvent("BluetoothSocketConnectError", mBadNews);
+ }
} catch (IOException connectException) {
Log.e("ConnectThread::run(): Error: Connection Unsuccessful");
cancel();
+ mEventFacade.postEvent("BluetoothSocketConnectError", mBadNews);
return;
}
}
diff --git a/Common/src/com/googlecode/android_scripting/facade/bluetooth/GattClientFacade.java b/Common/src/com/googlecode/android_scripting/facade/bluetooth/GattClientFacade.java
index e2286724..176dd303 100644
--- a/Common/src/com/googlecode/android_scripting/facade/bluetooth/GattClientFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/bluetooth/GattClientFacade.java
@@ -270,6 +270,21 @@ public class GattClientFacade extends RpcReceiver {
}
/**
+ * Reconnect to a Bluetooth GATT server
+ *
+ * @param index the bluetooth gatt index
+ * @throws Exception
+ */
+ @Rpc(description = "Reconnect a bluetooth gatt")
+ public void gattClientReconnect(@RpcParameter(name = "index") Integer index) throws Exception {
+ if (mBluetoothGattList.get(index) != null) {
+ mBluetoothGattList.get(index).connect();
+ } else {
+ throw new Exception("Invalid index input: " + index);
+ }
+ }
+
+ /**
* Disconnect a bluetooth gatt
*
* @param index the bluetooth gatt index
diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/AudioFileInfo.java b/Common/src/com/googlecode/android_scripting/facade/telephony/AudioFileInfo.java
new file mode 100644
index 00000000..bd5fd5ff
--- /dev/null
+++ b/Common/src/com/googlecode/android_scripting/facade/telephony/AudioFileInfo.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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.telephony;
+
+import androidx.annotation.NonNull;
+import com.google.auto.value.AutoValue;
+
+/** An AutoValue class to maintain the information of an audio file. */
+@AutoValue
+abstract class AudioFileInfo {
+ static AudioFileInfo create(String mime, int sampleRate, int channelCount) {
+ return new AutoValue_AudioFileInfo(mime, sampleRate, channelCount);
+ }
+
+ @NonNull
+ @Override
+ public final String toString() {
+ return String.format("{mime:%s, sample rate:%d, channel count:%d}",
+ mime(),
+ sampleRate(),
+ channelCount());
+ }
+
+ abstract String mime();
+ abstract int sampleRate();
+ abstract int channelCount();
+}
diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/InCallServiceImpl.java b/Common/src/com/googlecode/android_scripting/facade/telephony/InCallServiceImpl.java
index 0e1e8ca8..1629217a 100644
--- a/Common/src/com/googlecode/android_scripting/facade/telephony/InCallServiceImpl.java
+++ b/Common/src/com/googlecode/android_scripting/facade/telephony/InCallServiceImpl.java
@@ -16,11 +16,21 @@
package com.googlecode.android_scripting.facade.telephony;
+import static com.googlecode.android_scripting.facade.telephony.InCallServiceImpl.HandleVoiceThreadState.TERMINATE;
+import static com.googlecode.android_scripting.facade.telephony.InCallServiceImpl.HandleVoiceThreadState.RUN;
+import static com.googlecode.android_scripting.facade.telephony.RecordVoiceInCall.MONO_CHANNEL;
+import static com.googlecode.android_scripting.facade.telephony.RecordVoiceInCall.SAMPLE_RATE_16K;
+import static com.googlecode.android_scripting.facade.telephony.RecordVoiceInCall.SAMPLE_RATE_48K;
+import static com.googlecode.android_scripting.facade.telephony.RecordVoiceInCall.STEREO_CHANNEL;
+
+import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
+import java.io.File;
+import android.content.Context;
import android.telecom.Call;
import android.telecom.Call.Details;
import android.telecom.CallAudioState;
@@ -30,6 +40,8 @@ import android.telecom.Phone;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.telecom.VideoProfile.CameraCapabilities;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
import com.googlecode.android_scripting.Log;
@@ -39,6 +51,27 @@ public class InCallServiceImpl extends InCallService {
private static InCallServiceImpl sService = null;
+ private static PlayAudioInCall playAudioInCall;
+ private static RecordVoiceInCall recordVoiceInCall;
+
+ // The call is to play an audio file or record voice.
+ private static Call playRecordCall = null;
+
+ // A telephony device to route voice through mobile telphony network
+ private static AudioDeviceInfo audioTelephonyInfo = null;
+
+ // Indicates if the call is playing audio or not
+ private static HandleVoiceThreadState playAudioInCallState = TERMINATE;
+ // Indicates if the call is recording voice or not
+ private static HandleVoiceThreadState recordVoiceInCallState = TERMINATE;
+
+ private static AudioManager mAudioManager = null;
+
+ // The audio file is to play audio on the route of telephony network
+ private static File playAudioFile;
+ // The audio file is to store voice wav data on the route of telephony network
+ private static File recordVoiceFile;
+
public static InCallServiceImpl getService() {
return sService;
}
@@ -542,6 +575,14 @@ public class InCallServiceImpl extends InCallService {
}
}
+ /** Indicates and controls the state of the audio playing or voice recording thread. */
+ enum HandleVoiceThreadState {
+ /** The audio playing/voice recording thread is terminated. */
+ TERMINATE,
+ /** The audio playing/voice recording thread is running. */
+ RUN
+ }
+
/*
* TODO: b/26272583 Refactor so that these are instance members of the
* incallservice. Then we can perform null checks using the design pattern
@@ -560,6 +601,10 @@ public class InCallServiceImpl extends InCallService {
CallCallback callCallback = new CallCallback(id, CallCallback.EVENT_NONE);
call.registerCallback(callCallback);
+ // Make sure the first call is used to play or record voice
+ if (playRecordCall == null) {
+ playRecordCall = call;
+ }
VideoCall videoCall = call.getVideoCall();
VideoCallCallback videoCallCallback = null;
@@ -590,6 +635,7 @@ public class InCallServiceImpl extends InCallService {
*/
if (sService == null) {
sService = this;
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
}
else if (sService != this) {
Log.e("Multiple InCall Services Active in SL4A!");
@@ -606,6 +652,14 @@ public class InCallServiceImpl extends InCallService {
mCallContainerMap.remove(id);
+ if (getCallId(call).equals(getCallId(playRecordCall))) {
+ Log.d("Terminate the call for playing/recording.");
+ playAudioInCallState = TERMINATE;
+ playRecordCall = null;
+ recordVoiceInCallState = TERMINATE;
+ recordVoiceInCall = null;
+ }
+
CallListener.onCallRemoved(id, call);
if (mCallContainerMap.size() == 0) {
@@ -1450,6 +1504,140 @@ public class InCallServiceImpl extends InCallService {
return propertyList;
}
+ /**
+ * Plays an audio file specified by {@code audioFileName} during a phone call.
+ *
+ * The method first checks if {@link #Call}, {@code audioFileName} and {@link #AudioDeviceInfo}
+ * exist. Finally, it creates a {@link #PlayAudioInCall} which creates a thread to perform
+ * audio playing.
+ * The method is called by {@link TelephonyManagerFacade#telephonyPlayAudioFile()}.
+ *
+ * @return {@code true} if the audio file is successfully played. Otherwise, {@code false}
+ */
+ public static boolean playAudioFile(String audioFileName) {
+ Log.d(String.format("Playing audio file \"%s\"...", audioFileName));
+ if (playAudioInCallState.equals(RUN)) {
+ Log.d("Playing is ongoing!");
+ return false;
+ }
+ if (getService() == null) {
+ Log.d("InCallService isn't activated yet");
+ return false;
+ }
+ audioTelephonyInfo = getAudioDeviceInfo();
+ if (audioTelephonyInfo == null) {
+ Log.d("No Telephony AudioDeviceInfo!");
+ return false;
+ }
+ playAudioFile = new File(getService().getFilesDir(), audioFileName);
+ if (!playAudioFile.exists()) {
+ Log.d(String.format("%s not found in files folder!",audioFileName));
+ return false;
+ }
+ playAudioInCall = new PlayAudioInCall(mEventFacade,
+ playRecordCall, playAudioFile, audioTelephonyInfo);
+ return playAudioInCall.playAudioFile();
+ }
+
+ /** Gets the audio telephony device during in a call.
+ *
+ * @return null if not found audio telephony device. Otherwise, an instance of
+ * {@link #AudioDeviceInfo}
+ * */
+ public static AudioDeviceInfo getAudioDeviceInfo() {
+ AudioDeviceInfo[] audioDeviceInfoList = mAudioManager.getDevices(
+ AudioManager.GET_DEVICES_OUTPUTS);
+ for (AudioDeviceInfo info : audioDeviceInfoList) {
+ if (info.getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
+ Log.d(String.format("Found audio telephony device: %d", info.getType()));
+ return info;
+ }
+ }
+ return null;
+ }
+
+ public static HandleVoiceThreadState getPlayAudioInCallState() {
+ return playAudioInCallState;
+ }
+
+ public static void setPlayAudioInCallState(HandleVoiceThreadState state) {
+ playAudioInCallState = state;
+ }
+
+ public static void stopPlayAudioFile() {
+ if (playAudioInCallState.equals(RUN) && playAudioInCall != null) {
+ Log.d("Stop playing audio successfully!");
+ playAudioInCallState = TERMINATE;
+ }
+ }
+
+ /**
+ * Records voice during a phone call.
+ *
+ * The method checks the following items before creating a thread to record voice.
+ * <ol>
+ * <li>Check recoding state. If there is already a voice recording, ignore the request.</li>
+ * <li>Check if call has been established.</li>
+ * <li>Check the input sample rate and channel count to meet constraints.</li>
+ * <li>Check if the record wav file is created successfully.</li>
+ * </ol>
+ * @param recordWavFile indicates the wav file name of the recording voice
+ * @param sampleRate indicates sampling rate of the recording voice
+ * @param channelCount indicates voice channel number to be recorded
+ * @return {@code true} if voice is successfully recorded. Otherwise, {@code false}
+ */
+ public static boolean recordVoice(
+ String recordWavFile, int sampleRate, int channelCount, boolean cancelNoiseEcho) {
+ Log.d(String.format("Recording voice to the \"%s\" file...", recordWavFile));
+ if (getRecordVoiceInCallState().equals(RUN)) {
+ Log.d("Recording is ongoing!");
+ return false;
+ }
+ if (getService() == null) {
+ Log.d("InCallService isn't activated yet");
+ return false;
+ }
+ if (sampleRate != SAMPLE_RATE_16K && sampleRate != SAMPLE_RATE_48K) {
+ Log.e(String.format("Don't support sample rate: %d", sampleRate));
+ return false;
+ }
+ if (channelCount != MONO_CHANNEL && channelCount != STEREO_CHANNEL) {
+ Log.e(String.format("Don't support channel count: %d", channelCount));
+ return false;
+ }
+ recordVoiceFile = new File(getService().getFilesDir(), recordWavFile);
+ if (!recordVoiceFile.exists()) {
+ try {
+ Log.d(String.format("Creates a empty %s wav file to store voice data!",
+ recordWavFile));
+ recordVoiceFile.createNewFile();
+ } catch (IOException e) {
+ Log.e(String.format("Failed to create %s wav file!", recordWavFile));
+ return false;
+ }
+ }
+ Log.d(String.format("The voice recording info: wav file: %s, Sampling rate: %d, channel count: %d",
+ recordWavFile, sampleRate, channelCount));
+ recordVoiceInCall = new RecordVoiceInCall(mEventFacade, playRecordCall, recordVoiceFile,
+ sampleRate, channelCount, cancelNoiseEcho);
+ return recordVoiceInCall.recordVoice();
+ }
+
+ public static void stopRecordVoice() {
+ if (getRecordVoiceInCallState().equals(RUN) && playAudioInCall != null) {
+ Log.d("Stop recording voice successfully!");
+ setRecordVoiceInCallState(TERMINATE);
+ }
+ }
+
+ public static HandleVoiceThreadState getRecordVoiceInCallState() {
+ return recordVoiceInCallState;
+ }
+
+ public static void setRecordVoiceInCallState(HandleVoiceThreadState state) {
+ recordVoiceInCallState = state;
+ }
+
public static String getCallPresentationInfoString(int presentation) {
switch (presentation) {
case TelecomManager.PRESENTATION_ALLOWED:
diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/PlayAudioInCall.java b/Common/src/com/googlecode/android_scripting/facade/telephony/PlayAudioInCall.java
new file mode 100644
index 00000000..f708f467
--- /dev/null
+++ b/Common/src/com/googlecode/android_scripting/facade/telephony/PlayAudioInCall.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * 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.telephony;
+
+import static com.googlecode.android_scripting.facade.telephony.InCallServiceImpl.HandleVoiceThreadState.TERMINATE;
+import static com.googlecode.android_scripting.facade.telephony.InCallServiceImpl.HandleVoiceThreadState.RUN;
+import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
+import static android.media.AudioAttributes.CONTENT_TYPE_MUSIC;
+import static android.media.AudioAttributes.USAGE_MEDIA;
+import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
+import static android.os.Build.VERSION;
+import static android.os.Build.VERSION_CODES;
+
+import com.googlecode.android_scripting.facade.EventFacade;
+import com.googlecode.android_scripting.Log;
+import android.media.AudioDeviceInfo;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioTrack;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.telecom.Call;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.File;
+
+/**
+ * The class handles playing an audio file on the route of the telephony network during a phone
+ * call.
+ */
+public class PlayAudioInCall {
+ private static Thread playAudioThread = null;
+ private AudioDeviceInfo audioDeviceInfo;
+ private File audioFile;
+ private AudioFileInfo audioFileInfo;
+ private Call call;
+ private EventFacade eventFacade;
+
+ PlayAudioInCall(EventFacade eventFacade,
+ Call call,
+ File audioFile,
+ AudioDeviceInfo audioDeviceInfo) {
+ this.eventFacade = eventFacade;
+ this.call = call;
+ this.audioFile = audioFile;
+ this.audioDeviceInfo = audioDeviceInfo;
+ Log.d(String.format("eventFacade=%s, call=%s, audioFile=%s, audioDeviceInfo=%d",
+ this.eventFacade, this.call, this.audioFile, this.audioDeviceInfo.getId()));
+ }
+
+ boolean playAudioFile() {
+ if (!setupAudioFileInfo())
+ return false;
+ return playAudio();
+ }
+
+ private boolean playAudio() {
+ AudioFormat audioFormat = getAudioFormat();
+ Log.d(String.format("Audio format: %s", audioFormat.toString()));
+ AudioTrack audioTrack = getAudioTrack(audioFormat, getAudioTrackBufferSize(audioFormat));
+ Log.d(String.format("Audio Track: %s",audioTrack.toString()));
+ if (!audioTrack.setPreferredDevice(audioDeviceInfo)) {
+ audioTrack.release();
+ return false;
+ }
+ Log.d(String.format("Set the preferred audio device to %d successfully",
+ audioDeviceInfo.getId()));
+ while (audioTrack.getState() != AudioTrack.STATE_INITIALIZED)
+ ;
+ Log.d(String.format("Audio track state: %s", audioTrack.getState()));
+ return createPlayAudioThread(audioTrack);
+ }
+
+ private boolean createPlayAudioThread(AudioTrack audioTrack) {
+ playAudioThread = new Thread(() -> {
+ byte[] audioRaw = new byte[512];
+ int readBytes;
+ try {
+ InCallServiceImpl.setPlayAudioInCallState(RUN);
+ InCallServiceImpl.muteCall(true);
+ InputStream inputStream = new FileInputStream(audioFile);
+ inputStream.read(audioRaw, 0, 44);
+ audioTrack.play();
+ while ((readBytes = inputStream.read(audioRaw)) != -1) {
+ audioTrack.write(audioRaw, 0, readBytes);
+ if (stopPlayAudio()) {
+ break;
+ }
+ }
+ Log.d("End Playing audio!");
+ inputStream.close();
+ audioTrack.stop();
+ audioTrack.release();
+ eventFacade.postEvent(TelephonyConstants.EventCallPlayAudioStateChanged,
+ new InCallServiceImpl.CallEvent<String>(InCallServiceImpl.getCallId(call),
+ TelephonyConstants.TELEPHONY_STATE_PLAY_AUDIO_END));
+ }
+ catch (IOException e) {
+ audioTrack.release();
+ Log.d(String.format("Failed to read audio file \"%s\"!", audioFile.getName()));
+ eventFacade.postEvent(TelephonyConstants.EventCallPlayAudioStateChanged,
+ new InCallServiceImpl.CallEvent<String>(InCallServiceImpl.getCallId(call),
+ TelephonyConstants.TELEPHONY_STATE_PLAY_AUDIO_FAIL));
+ }
+ finally {
+ InCallServiceImpl.muteCall(false);
+ InCallServiceImpl.setPlayAudioInCallState(TERMINATE);
+ }
+ });
+ playAudioThread.start();
+ return true;
+ }
+
+ private AudioTrack getAudioTrack(AudioFormat audioFormat, int bufferSize) {
+ return new AudioTrack.Builder().setAudioFormat(audioFormat).setBufferSizeInBytes(bufferSize)
+ .setAudioAttributes(getAudioAttributes()).setTransferMode(AudioTrack.MODE_STREAM).build();
+ }
+
+ private int getAudioTrackBufferSize(AudioFormat audioFormat) {
+ return AudioTrack.getMinBufferSize(
+ audioFormat.getSampleRate(),
+ audioFormat.getChannelMask(),
+ audioFormat.getEncoding());
+ }
+
+ private AudioAttributes getAudioAttributes() {
+ if (VERSION.SDK_INT >= VERSION_CODES.Q) {
+ Log.d("AudioAttributes above Android Q is used.");
+ return new AudioAttributes.Builder()
+ .setUsage(USAGE_VOICE_COMMUNICATION).build();
+ } else {
+ Log.d("AudioAttributes below Android Q is used.");
+ return new AudioAttributes.Builder()
+ .setContentType(CONTENT_TYPE_MUSIC)
+ .setFlags(FLAG_BYPASS_INTERRUPTION_POLICY)
+ .setUsage(USAGE_MEDIA).build();
+ }
+ }
+
+ private boolean setupAudioFileInfo() {
+ MediaExtractor extractor = new MediaExtractor();
+ try {
+ extractor.setDataSource(audioFile.getAbsolutePath());
+ } catch (IOException e) {
+ Log.d(String.format("Failed to set data source in MediaExtrator, %s", e.getMessage()));
+ return false;
+ }
+ extractor.selectTrack(0);
+ MediaFormat format = extractor.getTrackFormat(0);
+ audioFileInfo = AudioFileInfo.create(format.getString(MediaFormat.KEY_MIME),
+ format.getInteger(MediaFormat.KEY_SAMPLE_RATE),
+ format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+ Log.d(String.format("The media format is %s", audioFileInfo));
+ return true;
+ }
+
+ private AudioFormat getAudioFormat() {
+ int channelMask = audioFileInfo.channelCount() == 1 ? AudioFormat.CHANNEL_OUT_MONO
+ : AudioFormat.CHANNEL_OUT_STEREO;
+ return new AudioFormat.Builder().setChannelMask(channelMask)
+ .setSampleRate(audioFileInfo.sampleRate()).setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .build();
+ }
+
+ private boolean stopPlayAudio() {
+ if (InCallServiceImpl.getPlayAudioInCallState().equals(RUN)) {
+ return false;
+ }
+ Log.d("Stop playing audio!");
+ return true;
+ }
+}
diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/RecordVoiceInCall.java b/Common/src/com/googlecode/android_scripting/facade/telephony/RecordVoiceInCall.java
new file mode 100644
index 00000000..3b4adb67
--- /dev/null
+++ b/Common/src/com/googlecode/android_scripting/facade/telephony/RecordVoiceInCall.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * 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.telephony;
+
+import static com.googlecode.android_scripting.facade.telephony.InCallServiceImpl.HandleVoiceThreadState.RUN;
+import static com.googlecode.android_scripting.facade.telephony.InCallServiceImpl.HandleVoiceThreadState.TERMINATE;
+import static java.nio.ByteOrder.LITTLE_ENDIAN;
+
+import com.googlecode.android_scripting.Log;
+import com.googlecode.android_scripting.facade.EventFacade;
+import android.telecom.Call;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder.AudioSource;
+import android.media.audiofx.AcousticEchoCanceler;
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.NoiseSuppressor;
+import com.googlecode.android_scripting.facade.telephony.InCallServiceImpl.HandleVoiceThreadState;
+import java.io.File;
+import java.io.IOException;
+import java.io.FileOutputStream;
+import java.io.DataOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.io.RandomAccessFile;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * The class handles recording voice on the route of the telephony network during a phone
+ * call.
+ */public class RecordVoiceInCall {
+ public static final int SAMPLE_RATE_16K = 16000;
+ public static final int SAMPLE_RATE_48K = 48000;
+ public static final int MONO_CHANNEL = 1;
+ public static final int STEREO_CHANNEL = 2;
+ private static final int PCM_16_BITS = 16;
+ private EventFacade eventFacade;
+ private Call call;
+ private int sampleRate;
+ private int channelCount;
+ private File recordFile;
+ private AudioFileInfo audioFileInfo;
+ private AudioFormat audioFormat;
+ private NoiseSuppressor noiseSuppressor;
+ private AcousticEchoCanceler acousticEchoCanceler;
+ private int minRecordAudioBufferSize;
+ private boolean cancelNoiseEcho;
+ private static Thread recordVoiceThread = null;
+
+ RecordVoiceInCall(EventFacade eventFacade,
+ Call call,
+ File recordFile,
+ int sampleRate,
+ int channelCount,
+ boolean cancelNoiseEcho) {
+ this.eventFacade = eventFacade;
+ this.call = call;
+ this.recordFile = recordFile;
+ this.sampleRate = sampleRate;
+ this.channelCount = channelCount;
+ this.cancelNoiseEcho = cancelNoiseEcho;
+ Log.d(String.format("eventFacade=%s, call=%s, recordFile=%s, sampleRate=%d, channelCount=%d",
+ this.eventFacade, this.call, this.recordFile, this.sampleRate, this.channelCount));
+ }
+
+ /**Handles functional flows of voice recording and exposes to be invoked by users.*/
+ boolean recordVoice() {
+ setRecordAudioInfo();
+ setAudioFormat();
+ setMinRecordAudioBufferSize();
+ AudioRecord audioRecord = new AudioRecord.Builder()
+ .setAudioSource(AudioSource.VOICE_DOWNLINK)
+ .setAudioFormat(audioFormat)
+ .setBufferSizeInBytes(minRecordAudioBufferSize)
+ .build();
+ if (cancelNoiseEcho) {
+ enableNoiseSuppressor(audioRecord);
+ enableAcousticEchoCanceler(audioRecord);
+ }
+ return createRecordVoiceThread(audioRecord);
+ }
+
+ private void setRecordAudioInfo() {
+ audioFileInfo = AudioFileInfo.create("WAVE", sampleRate, channelCount);
+ }
+
+ /**Configures an object of {@code AudioFormat} needed for voice recording in a call.
+ * The minimal info is to provide channel count and voice encoding scheme.
+ */
+ private void setAudioFormat() {
+ int channelMask = audioFileInfo.channelCount() == 1 ? AudioFormat.CHANNEL_IN_MONO
+ : AudioFormat.CHANNEL_IN_STEREO;
+ audioFormat = new AudioFormat.Builder().setChannelMask(channelMask)
+ .setSampleRate(audioFileInfo.sampleRate()).setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .build();
+ Log.d(String.format(
+ "Audio format for recording, channel mask: %d, sample rate: %d, encoding: PCM_16bit",
+ channelMask, sampleRate));
+ }
+
+ /**Acquires minimal buffer size for sampling voice data.*/
+ private void setMinRecordAudioBufferSize() {
+ minRecordAudioBufferSize = AudioRecord.getMinBufferSize(audioFormat.getSampleRate(),
+ audioFormat.getChannelCount(),
+ audioFormat.getEncoding());
+ Log.d(String.format("Buffer size to record voice data: %d", minRecordAudioBufferSize));
+ }
+
+ private void enableNoiseSuppressor(AudioRecord audioRecord) {
+ if (NoiseSuppressor.isAvailable()) {
+ noiseSuppressor = NoiseSuppressor.create(audioRecord.getAudioSessionId());
+ if (noiseSuppressor.setEnabled(true) != AudioEffect.SUCCESS) {
+ Log.w("Failed to enable noiseSuppressor!");
+ return;
+ }
+ Log.d("Enable noiseSuppressor!");
+ return;
+ }
+ Log.d("Noise suppressor is not available!");
+ }
+
+ private void enableAcousticEchoCanceler(AudioRecord audioRecord) {
+ if (AcousticEchoCanceler.isAvailable()) {
+ acousticEchoCanceler = AcousticEchoCanceler.create(audioRecord.getAudioSessionId());
+ if (acousticEchoCanceler.setEnabled(true) != AudioEffect.SUCCESS) {
+ Log.w("Failed to enable AcousticEchoCanceler");
+ return;
+ }
+ Log.d("Enable AcousticEchoCanceler!");
+ return;
+ }
+ Log.d("AcousticEchoCanceler is not available!");
+ }
+
+ /**Creates a background thread to perform voice recording.*/
+ private boolean createRecordVoiceThread(AudioRecord audioRecord) {
+ recordVoiceThread = new Thread(()-> {
+ int totalVoiceBytes = 0;
+ try {
+ InCallServiceImpl.setRecordVoiceInCallState(RUN);
+ InCallServiceImpl.muteCall(true);
+ audioRecord.startRecording();
+ FileOutputStream outputStream = new FileOutputStream(recordFile);
+ DataOutputStream recordVoiceOutStream = new DataOutputStream(outputStream);
+
+ writeWaveHeader(recordVoiceOutStream, totalVoiceBytes);
+ short[] buffer = new short[minRecordAudioBufferSize];
+
+ while (InCallServiceImpl.getRecordVoiceInCallState().equals(RUN)) {
+ int shortsRead = audioRecord.read(buffer, 0, minRecordAudioBufferSize);
+ totalVoiceBytes += shortsRead * 2;
+ for (int count = 0; count < shortsRead; count++) {
+ recordVoiceOutStream.writeShort(getShortByOrder(buffer[count], LITTLE_ENDIAN));
+ }
+ }
+ recordVoiceOutStream.flush();
+ recordVoiceOutStream.close();
+ correctWaveHeaderChunkSize(totalVoiceBytes);
+ Log.d("End recording voice!");
+ eventFacade.postEvent(TelephonyConstants.EventCallRecordVoiceStateChanged,
+ new InCallServiceImpl.CallEvent<String>(InCallServiceImpl.getCallId(call),
+ TelephonyConstants.TELEPHONY_STATE_RECORD_VOICE_END));
+
+ } catch (IOException e) {
+ Log.d(String.format("Failed to record voice to \"%s\"!", recordFile.getName()));
+ eventFacade.postEvent(TelephonyConstants.EventCallPlayAudioStateChanged,
+ new InCallServiceImpl.CallEvent<String>(InCallServiceImpl.getCallId(call),
+ TelephonyConstants.TELEPHONY_STATE_PLAY_AUDIO_FAIL));
+
+ } finally {
+ InCallServiceImpl.muteCall(false);
+ releaseRecordSources(audioRecord);
+ }
+ });
+ recordVoiceThread.start();
+ return true;
+ }
+
+ private void releaseRecordSources(AudioRecord audioRecord) {
+ audioRecord.stop();
+ audioRecord.release();
+ if (noiseSuppressor != null) {
+ noiseSuppressor.release();
+ }
+ if (acousticEchoCanceler != null) {
+ acousticEchoCanceler.release();
+ }
+ InCallServiceImpl.setRecordVoiceInCallState(TERMINATE);
+ }
+ /**Creates and writes wave header to the beginning of the wave file.*/
+ private void writeWaveHeader(DataOutputStream dataOutputStream, int totalBytes)
+ throws IOException {
+ /* 1-4 */
+ dataOutputStream.write("RIFF".getBytes(StandardCharsets.UTF_8));
+ /* 5-8 Write Chunk Size ~= the byte size of voice data + 36 */
+ dataOutputStream.writeInt(getIntByOrder(totalBytes + 36, LITTLE_ENDIAN));
+ /* 9-12 */
+ dataOutputStream.write("WAVE".getBytes(StandardCharsets.UTF_8));
+ /* 13-16 */
+ dataOutputStream.write("fmt ".getBytes(StandardCharsets.UTF_8));
+ /* 17-20 Writes SubChunk1Size */
+ dataOutputStream.writeInt(getIntByOrder(16, LITTLE_ENDIAN));
+ /* 21-22 Writes audio format, PCM: 1 */
+ dataOutputStream.writeShort(getShortByOrder((short) 1, LITTLE_ENDIAN));
+ /* 23-24 Writes number of channels */
+ dataOutputStream.writeShort(
+ getShortByOrder((short) audioFileInfo.channelCount(), LITTLE_ENDIAN));
+ /* 25-28 Writes sampling rate */
+ dataOutputStream.writeInt(
+ getIntByOrder(audioFileInfo.sampleRate(), LITTLE_ENDIAN));
+ /* 29-32 Writes byte rate */
+ int byteRate = audioFileInfo.sampleRate() * audioFileInfo.channelCount() * PCM_16_BITS/2;
+ dataOutputStream.writeInt(
+ getIntByOrder(byteRate, LITTLE_ENDIAN));
+ /* 33-34 Writes block align */
+ int blockAlign = audioFileInfo.channelCount() * PCM_16_BITS / 2;
+ dataOutputStream.writeShort(
+ getShortByOrder((short) blockAlign, LITTLE_ENDIAN));
+ /* 35-36 Writes PCM bits */
+ dataOutputStream.writeShort(
+ getShortByOrder((short) PCM_16_BITS, LITTLE_ENDIAN));
+ /* 37-40 */
+ dataOutputStream.write("data".getBytes(StandardCharsets.UTF_8));
+ /* 41-44 SubChunk2Size ~= the byte size of voice data */
+ dataOutputStream.writeInt(
+ getIntByOrder(totalBytes, LITTLE_ENDIAN));
+ }
+
+ private void correctWaveHeaderChunkSize(int totalVoiceBytes) throws IOException {
+ RandomAccessFile randomVoiceAccessFile = new RandomAccessFile(recordFile, "rw");
+ writeWaveHeaderChunkSize(randomVoiceAccessFile, totalVoiceBytes);
+ writeWaveHeaderSubChunk2Size(randomVoiceAccessFile, totalVoiceBytes);
+ randomVoiceAccessFile.close();
+ }
+
+ /**A wav file consists of two chunks. The first chunk is the header data which describes
+ * the sample rate, channel count, the size of voice data and so on. See {@code #writeWaveHeader}.
+ */
+ private void writeWaveHeaderChunkSize(RandomAccessFile randomAccessFile, int totalVoiceBytes)
+ throws IOException {
+ randomAccessFile.seek(4);
+ randomAccessFile.write(intToBytes(totalVoiceBytes + 36, LITTLE_ENDIAN), 0, 4);
+ }
+
+ /**A wav file consists of two chunks. The second chunk is the voice data.*/
+ private void writeWaveHeaderSubChunk2Size(RandomAccessFile randomAccessFile, int totalVoiceBytes)
+ throws IOException {
+ randomAccessFile.seek(40);
+ randomAccessFile.write(intToBytes(totalVoiceBytes, LITTLE_ENDIAN), 0, 4);
+ }
+
+ /**A short type of integer can be represented with little endian or big endian.*/
+ private short getShortByOrder(short value, ByteOrder byteOrder) {
+ byte[] bytes = ByteBuffer.allocate(2).putShort(value).array();
+ return ByteBuffer.wrap(bytes).order(byteOrder).getShort();
+ }
+
+ /**Converts integer to byte array.*/
+ private byte[] intToBytes(int value, ByteOrder byteOrder) {
+ return ByteBuffer.allocate(4).order(byteOrder).putInt(value).array();
+ }
+
+ /**An integer type of integer can be represented with little endian or big endian.*/
+ private int getIntByOrder(int value, ByteOrder byteOrder) {
+ byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
+ return ByteBuffer.wrap(bytes).order(byteOrder).getInt();
+ }
+}
diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/SmsFacade.java b/Common/src/com/googlecode/android_scripting/facade/telephony/SmsFacade.java
index 1de17161..1c8724f2 100644
--- a/Common/src/com/googlecode/android_scripting/facade/telephony/SmsFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/telephony/SmsFacade.java
@@ -740,7 +740,7 @@ public class SmsFacade extends RpcReceiver {
}
private PendingIntent createBroadcastPendingIntent(String intentAction, Uri messageUri) {
- Intent intent = new Intent(intentAction, messageUri);
+ Intent intent = new Intent(intentAction, messageUri).setPackage(mService.getPackageName());
return PendingIntent.getBroadcast(mService, 0, intent, PendingIntent.FLAG_MUTABLE);
}
diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyConstants.java b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyConstants.java
index d5e96335..591542f6 100644
--- a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyConstants.java
+++ b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyConstants.java
@@ -244,6 +244,10 @@ public class TelephonyConstants {
public static final String TELEPHONY_STATE_IDLE = "IDLE";
public static final String TELEPHONY_STATE_OFFHOOK = "OFFHOOK";
public static final String TELEPHONY_STATE_UNKNOWN = "UNKNOWN";
+ public static final String TELEPHONY_STATE_PLAY_AUDIO_END = "PLAYAUDIOEND";
+ public static final String TELEPHONY_STATE_PLAY_AUDIO_FAIL = "PLAYAUDIOFAIL";
+ public static final String TELEPHONY_STATE_RECORD_VOICE_END = "RECORDVOICEEND";
+ public static final String TELEPHONY_STATE_RECORD_VOICE_FAIL = "RECORDVOICEFAIL";
/**
* Constant for TTY Mode
@@ -401,6 +405,8 @@ public class TelephonyConstants {
public static final String EventSrvccStateChanged = "SrvccStateChanged";
public static final String EventMessageWaitingIndicatorChanged = "MessageWaitingIndicatorChanged";
public static final String EventPhysicalChannelConfigChanged = "PhysicalChannelConfigChanged";
+ public static final String EventCallPlayAudioStateChanged = "CallPlayAudioStateChanged";
+ public static final String EventCallRecordVoiceStateChanged = "CallRecordVoiceStateChanged";
/**
* Constants for OnStartTetheringCallback
diff --git a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyManagerFacade.java b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyManagerFacade.java
index e60e5f15..f68647ab 100644
--- a/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyManagerFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/telephony/TelephonyManagerFacade.java
@@ -32,10 +32,12 @@ import android.telephony.CellInfo;
import android.telephony.CellLocation;
import android.telephony.NeighboringCellInfo;
import android.telephony.PhoneStateListener;
+import android.telephony.RadioAccessFamily;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.PinResult;
import com.android.internal.telephony.RILConstants;
@@ -190,7 +192,10 @@ public class TelephonyManagerFacade extends RpcReceiver {
@Rpc(description = "Get network preference for subscription.")
public String telephonyGetPreferredNetworkTypesForSubscription(
@RpcParameter(name = "subId") Integer subId) {
- int networkPreferenceInt = mTelephonyManager.getPreferredNetworkType(subId);
+ int networkPreferenceInt =
+ RadioAccessFamily.getNetworkTypeFromRaf(
+ (int) mTelephonyManager.createForSubscriptionId(
+ subId).getAllowedNetworkTypesBitmask());
return TelephonyUtils.getNetworkModeStringfromInt(networkPreferenceInt);
}
@@ -1006,6 +1011,21 @@ public class TelephonyManagerFacade extends RpcReceiver {
}
/**
+ * Check if the Subscription ID is valid.
+ * @param subId the subscription ID
+ * @return {true} if subId is valid, {false} otherwise.
+ */
+ @Rpc(description = "Check if the Subscription ID is valid.")
+ public boolean telephonyIsSubscriptionIdValid(
+ @RpcParameter(name = "subId") Integer subId){
+ if (subId == null || !SubscriptionManager.isValidSubscriptionId(subId)) {
+ Log.e("Invalid or null subscription ID");
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Supply the puk code and pin for locked SIM.
* @param puk the puk code string
* @param pin the puk pin string
@@ -1019,6 +1039,25 @@ public class TelephonyManagerFacade extends RpcReceiver {
}
/**
+ * Supply the puk code and pin for locked SIM of specified subscription ID.
+ * @param subId the subscription ID
+ * @param puk the puk code string
+ * @param pin the puk pin string
+ * @return true or false for supplying the puk code and pin successfully or unsuccessfully.
+ */
+ @Rpc(description = "Supply Puk and Pin for locked SIM " +
+ "for specified subscription ID.")
+ public boolean telephonySupplyPukForSubscription(
+ @RpcParameter(name = "subId") Integer subId,
+ @RpcParameter(name = "puk") String puk,
+ @RpcParameter(name = "pin") String pin) {
+ if (!telephonyIsSubscriptionIdValid(subId)) {
+ return false;
+ }
+ return mTelephonyManager.createForSubscriptionId(subId).supplyPuk(puk, pin);
+ }
+
+ /**
* Supply pin for locked SIM.
* @param pin the puk pin string
* @return true or false for supplying the pin successfully or unsuccessfully.
@@ -1029,6 +1068,84 @@ public class TelephonyManagerFacade extends RpcReceiver {
return mTelephonyManager.supplyPin(pin);
}
+ /**
+ * Supply pin for locked SIM of specified subscription ID.
+ * @param subId the subscription ID
+ * @param pin the puk pin string
+ * @return true or false for supplying the pin successfully or unsuccessfully.
+ */
+ @Rpc(description = "Supply Pin for locked SIM " +
+ "for specified subscription ID.")
+ public boolean telephonySupplyPinForSubscription(
+ @RpcParameter(name = "subId") Integer subId,
+ @RpcParameter(name = "pin") String pin) {
+ if (!telephonyIsSubscriptionIdValid(subId)) {
+ return false;
+ }
+ return mTelephonyManager.createForSubscriptionId(subId).supplyPin(pin);
+ }
+
+ /**
+ * Enable or disable the ICC PIN lock of specified subscription ID.
+ * @param subId the subscription ID
+ * @param enabled "true" for enable, "false" for disable.
+ * @param pin needed to enable or disable the ICC PIN lock
+ * @return true or false for enable/disable the pin successfully or unsuccessfully.
+ */
+ @Rpc(description = "Enable or disable the ICC PIN lock " +
+ "for specified subscription ID.")
+ public boolean telephonySetIccLockEnabledForSubscription(
+ @RpcParameter(name = "subId") Integer subId,
+ @RpcParameter(name = "enabled") Boolean enabled,
+ @RpcParameter(name = "pin") String pin) {
+ if (!telephonyIsSubscriptionIdValid(subId)) {
+ return false;
+ }
+ PinResult result= mTelephonyManager.createForSubscriptionId(subId).setIccLockEnabled(enabled,pin);
+ if(result != null) {
+ return (result.getResult() == PinResult.PIN_RESULT_TYPE_SUCCESS);
+ }
+ return false;
+ }
+
+ /**
+ * Check whether ICC PIN lock is enabled.
+ * @param subId the subscription ID
+ * @return true or false for changing the ICC lock PIN successfully or unsuccessfully.
+ */
+ @Rpc(description = "Check whether ICC PIN lock is enabled " +
+ "for specified subscription ID.")
+ public boolean telephonyIsIccLockEnabled(
+ @RpcParameter(name = "subId") Integer subId) {
+ if (!telephonyIsSubscriptionIdValid(subId)) {
+ return false;
+ }
+ return mTelephonyManager.createForSubscriptionId(subId).isIccLockEnabled();
+ }
+
+ /**
+ * Change the ICC lock PIN of specified subscription ID.
+ * @param subId the subscription ID
+ * @param oldPin is the old PIN.
+ * @param newPin is the new PIN.
+ * @return true or false for changing the ICC lock PIN successfully or unsuccessfully.
+ */
+ @Rpc(description = "Change the ICC lock PIN " +
+ "for specified subscription ID.")
+ public boolean telephonyChangeIccLockPinForSubscription(
+ @RpcParameter(name = "subId") Integer subId,
+ @RpcParameter(name = "oldPin") String oldPin,
+ @RpcParameter(name = "newPin") String newPin) {
+ if (!telephonyIsSubscriptionIdValid(subId)) {
+ return false;
+ }
+ PinResult result= mTelephonyManager.createForSubscriptionId(subId).changeIccLockPin(oldPin,newPin);
+ if(result != null) {
+ return (result.getResult() == PinResult.PIN_RESULT_TYPE_SUCCESS);
+ }
+ return false;
+ }
+
@Rpc(description = "Returns the unique subscriber ID (such as IMSI) " +
"for default subscription ID, or null if unavailable")
public String telephonyGetSubscriberId() {
@@ -1172,7 +1289,7 @@ public class TelephonyManagerFacade extends RpcReceiver {
public void telephonySetCellInfoListRate(
@RpcParameter(name = "rate") Integer rate
) {
- mTelephonyManager.setCellInfoListRate(rate);
+ mTelephonyManager.setCellInfoListRate(rate, SubscriptionManager.getDefaultSubscriptionId());
}
/**
@@ -1513,6 +1630,53 @@ public class TelephonyManagerFacade extends RpcReceiver {
}
/**
+ * Plays an audio file specified by {@code audioFileName} during a phone call.
+ *
+ * @return {@code true} if the audio file is successfully played.
+ */
+ @Rpc(description = "Plays the specified audio file during a phone call")
+ public boolean telephonyPlayAudioFile(
+ @RpcParameter(name = "audioFileName", description = "the audio file in the app's files folder")
+ String audioFileName) {
+ Log.d(String.format("Playing audio file \"%s\"...", audioFileName));
+ InCallServiceImpl.setEventFacade(mEventFacade);
+ return InCallServiceImpl.playAudioFile(audioFileName);
+ }
+
+ /** Stops playing an audio file during a call. */
+ @Rpc(description = "Stops playing audio file during a phone call")
+ public void telephonyStopPlayingAudioFile() {
+ InCallServiceImpl.stopPlayAudioFile();
+ }
+
+ /**
+ * Records voice and writes to a wav file specified by {@code recordFileName}
+ * during a phone call.
+ *
+ * @return {@code true} if voice is successfully recorded
+ */
+ @Rpc(description = "Records voice and writes to a wav file during a phone call")
+ public boolean telephonyRecordVoice(
+ @RpcParameter(name = "recordFileName", description = "The recorded voice file name")
+ String recordFileName,
+ @RpcParameter(name = "sampleRate", description = "sampling rate of voice data")
+ @RpcDefault("16000") Integer sampleRate,
+ @RpcParameter(name = "channelCount", description = "channel number of voice to record")
+ @RpcDefault("1") Integer channelCount,
+ @RpcParameter(name = "cancelNoiseEcho", description = "enable echo canceler and noise suppressor")
+ @RpcDefault("false") Boolean cancelNoiseEcho) {
+ Log.d(String.format("Recording voice to the \"%s\" file...", recordFileName));
+ InCallServiceImpl.setEventFacade(mEventFacade);
+ return InCallServiceImpl.recordVoice(recordFileName, sampleRate, channelCount, cancelNoiseEcho);
+ }
+
+ /** Stops recording voice during a phone call.*/
+ @Rpc(description = "Stops recording voice during a call.")
+ public void telephonyStopRecordVoice() {
+ InCallServiceImpl.stopRecordVoice();
+ }
+
+ /**
* Get the list of Forbidden PLMNs stored on the USIM
* profile of the SIM for the default subscription.
*/
diff --git a/Common/src/com/googlecode/android_scripting/facade/uwb/UwbManagerFacade.java b/Common/src/com/googlecode/android_scripting/facade/uwb/UwbManagerFacade.java
index 5ca7ff83..03a9a29c 100644
--- a/Common/src/com/googlecode/android_scripting/facade/uwb/UwbManagerFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/uwb/UwbManagerFacade.java
@@ -26,6 +26,10 @@ import android.uwb.RangingSession;
import android.uwb.UwbAddress;
import android.uwb.UwbManager;
+import com.google.uwb.support.ccc.CccOpenRangingParams;
+import com.google.uwb.support.ccc.CccParams;
+import com.google.uwb.support.ccc.CccPulseShapeCombo;
+import com.google.uwb.support.ccc.CccRangingStartedParams;
import com.google.uwb.support.fira.FiraOpenSessionParams;
import com.google.uwb.support.fira.FiraParams;
import com.google.uwb.support.fira.FiraRangingReconfigureParams;
@@ -318,6 +322,77 @@ public class UwbManagerFacade extends RpcReceiver {
return builder.build();
}
+ private CccRangingStartedParams generateCccRangingStartedParams(JSONObject j)
+ throws JSONException {
+ if (j == null) {
+ return null;
+ }
+ CccRangingStartedParams.Builder builder = new CccRangingStartedParams.Builder();
+ if (j.has("stsIndex")) {
+ builder.setStartingStsIndex(j.getInt("stsIndex"));
+ }
+ if (j.has("uwbTime")) {
+ builder.setUwbTime0(j.getInt("uwbTime"));
+ }
+ if (j.has("hopModeKey")) {
+ builder.setHopModeKey(j.getInt("hopModeKey"));
+ }
+ if (j.has("syncCodeIndex")) {
+ builder.setSyncCodeIndex(j.getInt("syncCodeIndex"));
+ }
+ if (j.has("ranMultiplier")) {
+ builder.setRanMultiplier(j.getInt("ranMultiplier"));
+ }
+
+ return builder.build();
+ }
+
+ private CccOpenRangingParams generateCccOpenRangingParams(JSONObject j) throws JSONException {
+ if (j == null) {
+ return null;
+ }
+ CccOpenRangingParams.Builder builder = new CccOpenRangingParams.Builder();
+ builder.setProtocolVersion(CccParams.PROTOCOL_VERSION_1_0);
+ if (j.has("sessionId")) {
+ builder.setSessionId(j.getInt("sessionId"));
+ }
+ if (j.has("uwbConfig")) {
+ builder.setUwbConfig(j.getInt("uwbConfig"));
+ }
+ if (j.has("ranMultiplier")) {
+ builder.setRanMultiplier(j.getInt("ranMultiplier"));
+ }
+ if (j.has("channel")) {
+ builder.setChannel(j.getInt("channel"));
+ }
+ if (j.has("chapsPerSlot")) {
+ builder.setNumChapsPerSlot(j.getInt("chapsPerSlot"));
+ }
+ if (j.has("responderNodes")) {
+ builder.setNumResponderNodes(j.getInt("responderNodes"));
+ }
+ if (j.has("slotsPerRound")) {
+ builder.setNumSlotsPerRound(j.getInt("slotsPerRound"));
+ }
+ if (j.has("hoppingMode")) {
+ builder.setHoppingConfigMode(j.getInt("hoppingMode"));
+ }
+ if (j.has("hoppingSequence")) {
+ builder.setHoppingSequence(j.getInt("hoppingSequence"));
+ }
+ if (j.has("syncCodeIndex")) {
+ builder.setSyncCodeIndex(j.getInt("syncCodeIndex"));
+ }
+ if (j.has("pulseShapeCombo")) {
+ JSONObject pulseShapeCombo = j.getJSONObject("pulseShapeCombo");
+ builder.setPulseShapeCombo(new CccPulseShapeCombo(
+ pulseShapeCombo.getInt("pulseShapeComboTx"),
+ pulseShapeCombo.getInt("pulseShapeComboRx")));
+ }
+
+ return builder.build();
+ }
+
private FiraOpenSessionParams generateFiraOpenSessionParams(JSONObject j) throws JSONException {
if (j == null) {
return null;
@@ -355,7 +430,7 @@ public class UwbManagerFacade extends RpcReceiver {
builder.setDestAddressList(Arrays.asList(destinationUwbAddresses));
}
if (j.has("initiationTimeMs")) {
- builder.setInitiationTimeMs(j.getInt("initiationTimeMs"));
+ builder.setInitiationTime(j.getInt("initiationTimeMs"));
}
if (j.has("slotDurationRstu")) {
builder.setSlotDurationRstu(j.getInt("slotDurationRstu"));
@@ -424,6 +499,22 @@ public class UwbManagerFacade extends RpcReceiver {
}
/**
+ * Open CCC UWB ranging session.
+ */
+ @Rpc(description = "Open CCC UWB ranging session")
+ public String openCccRangingSession(@RpcParameter(name = "config") JSONObject config)
+ throws JSONException {
+ RangingSessionCallback rangingSessionCallback = new RangingSessionCallback(
+ Event.EventAll.getType());
+ CccOpenRangingParams params = generateCccOpenRangingParams(config);
+ CancellationSignal cancellationSignal = mUwbManager.openRangingSession(
+ params.toBundle(), mExecutor, rangingSessionCallback);
+ String key = rangingSessionCallback.mId;
+ sRangingSessionCallbackMap.put(key, rangingSessionCallback);
+ return key;
+ }
+
+ /**
* Start UWB ranging.
*/
@Rpc(description = "Start UWB ranging")
@@ -433,6 +524,16 @@ public class UwbManagerFacade extends RpcReceiver {
}
/**
+ * Start CCC UWB ranging.
+ */
+ @Rpc(description = "Start CCC UWB ranging")
+ public void startCccRangingSession(String key, JSONObject config) throws JSONException {
+ RangingSessionCallback rangingSessionCallback = sRangingSessionCallbackMap.get(key);
+ CccRangingStartedParams params = generateCccRangingStartedParams(config);
+ rangingSessionCallback.rangingSession.start(params.toBundle());
+ }
+
+ /**
* Reconfigures UWB ranging session.
*/
@Rpc(description = "Reconfigure UWB ranging session")
diff --git a/Common/src/com/googlecode/android_scripting/facade/wifi/WifiAwareManagerFacade.java b/Common/src/com/googlecode/android_scripting/facade/wifi/WifiAwareManagerFacade.java
index 7586039a..85a72a76 100644
--- a/Common/src/com/googlecode/android_scripting/facade/wifi/WifiAwareManagerFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/wifi/WifiAwareManagerFacade.java
@@ -413,7 +413,7 @@ public class WifiAwareManagerFacade extends RpcReceiver {
new AwareAttachCallbackPostsEvents(sessionId, useIdInCallbackEventName),
(identityCb != null && identityCb.booleanValue())
? new AwareIdentityChangeListenerPostsEvents(sessionId,
- useIdInCallbackEventName) : null);
+ useIdInCallbackEventName) : null, false, null);
return sessionId;
}
}
diff --git a/Common/src/com/googlecode/android_scripting/facade/wifi/WifiManagerFacade.java b/Common/src/com/googlecode/android_scripting/facade/wifi/WifiManagerFacade.java
index 0b1318f1..2bc1f228 100755
--- a/Common/src/com/googlecode/android_scripting/facade/wifi/WifiManagerFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/wifi/WifiManagerFacade.java
@@ -2154,7 +2154,7 @@ public class WifiManagerFacade extends RpcReceiver {
if (bandList != null) {
// Build a JSON array of bands represented as operating classes
- Log.d("onFailure list of supported bands: " + bandList);
+ Log.d("onFailure list of supported bands: " + Arrays.toString(bandList));
JSONArray formattedBandList = new JSONArray();
for (int i = 0; i < bandList.length; i++) {
formattedBandList.put(bandList[i]);
diff --git a/Common/src/com/googlecode/android_scripting/facade/wifi/WifiScannerFacade.java b/Common/src/com/googlecode/android_scripting/facade/wifi/WifiScannerFacade.java
index bea4a824..300c98ce 100644
--- a/Common/src/com/googlecode/android_scripting/facade/wifi/WifiScannerFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/wifi/WifiScannerFacade.java
@@ -27,7 +27,6 @@ import android.net.wifi.WifiScanner.ScanData;
import android.net.wifi.WifiScanner.ScanSettings;
import android.os.Bundle;
import android.os.SystemClock;
-import android.provider.Settings.Global;
import android.provider.Settings.SettingNotFoundException;
import com.googlecode.android_scripting.Log;
@@ -400,7 +399,7 @@ public class WifiScannerFacade extends RpcReceiver {
@RpcParameter(name = "scanSettings") JSONObject scanSettings)
throws JSONException {
ScanSettings ss = parseScanSettings(scanSettings);
- Log.d("startWifiScannerScan with " + ss.channels);
+ Log.d("startWifiScannerScan with " + Arrays.toString(ss.channels));
WifiScanListener listener = genBackgroundWifiScanListener();
mScan.startBackgroundScan(ss, listener);
return listener.mIndex;
@@ -450,7 +449,7 @@ public class WifiScannerFacade extends RpcReceiver {
@RpcParameter(name = "scanSettings") JSONObject scanSettings)
throws JSONException {
ScanSettings ss = parseScanSettings(scanSettings);
- Log.d("startWifiScannerScan with " + ss.channels);
+ Log.d("startWifiScannerScan with " + Arrays.toString(ss.channels));
WifiScanListener listener = genWifiScanListener();
mScan.startScan(ss, listener);
return listener.mIndex;
diff --git a/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java b/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java
index bb90c69a..aead5979 100644
--- a/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java
+++ b/Common/src/com/googlecode/android_scripting/jsonrpc/JsonBuilder.java
@@ -1085,7 +1085,7 @@ public class JsonBuilder {
msg.put("iccId", data.getIccId());
msg.put("simSlotIndex", data.getSimSlotIndex());
msg.put("displayName", data.getDisplayName());
- msg.put("nameSource", data.getNameSource());
+ msg.put("nameSource", data.getDisplayNameSource());
msg.put("iconTint", data.getIconTint());
msg.put("number", data.getNumber());
msg.put("dataRoaming", data.getDataRoaming());
diff --git a/OWNERS b/OWNERS
index f4dea96a..367c156b 100644
--- a/OWNERS
+++ b/OWNERS
@@ -7,6 +7,7 @@ gmoturu@google.com
jaineelm@google.com
jpawlowski@google.com
krisr@google.com
+rahulsabnis@google.com
siyuanh@google.com
tturney@google.com
xianyuanjia@google.com
diff --git a/ScriptingLayerForAndroid/AndroidManifest.xml b/ScriptingLayerForAndroid/AndroidManifest.xml
index de7442df..e937bcff 100644
--- a/ScriptingLayerForAndroid/AndroidManifest.xml
+++ b/ScriptingLayerForAndroid/AndroidManifest.xml
@@ -132,6 +132,7 @@
android:icon="@drawable/sl4a_logo_48"
android:label="@string/application_title"
android:name=".Sl4aApplication"
+ android:testOnly="true"
android:theme="@android:style/Theme.DeviceDefault"
android:usesCleartextTraffic="true">
<activity android:name=".activity.ScriptManager" android:configChanges="keyboardHidden|orientation" android:windowSoftInputMode="adjustResize" android:launchMode="singleTop" android:exported="true">