diff options
Diffstat (limited to 'src/main/java/com/google/android/mobly/snippet/bundled')
7 files changed, 235 insertions, 14 deletions
diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/SmsSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/SmsSnippet.java index e8a84c9..362a1b7 100644 --- a/src/main/java/com/google/android/mobly/snippet/bundled/SmsSnippet.java +++ b/src/main/java/com/google/android/mobly/snippet/bundled/SmsSnippet.java @@ -84,7 +84,13 @@ public class SmsSnippet implements Snippet { if (message.length() > MAX_CHAR_COUNT_PER_SMS) { ArrayList<String> parts = mSmsManager.divideMessage(message); receiver.setExpectedMessageCount(parts.size()); - mContext.registerReceiver(receiver, new IntentFilter(SMS_SENT_ACTION)); + if (Build.VERSION.SDK_INT >= 33) { + mContext.registerReceiver(receiver, new IntentFilter(SMS_SENT_ACTION), null, + null, + Context.RECEIVER_EXPORTED); + } else { + mContext.registerReceiver(receiver, new IntentFilter(SMS_SENT_ACTION)); + } mSmsManager.sendMultipartTextMessage( /* destinationAddress= */ phoneNumber, /* scAddress= */ null, @@ -107,7 +113,13 @@ public class SmsSnippet implements Snippet { /* intent= */ new Intent(SMS_SENT_ACTION), /* flags= */ PendingIntent.FLAG_IMMUTABLE); receiver.setExpectedMessageCount(1); - mContext.registerReceiver(receiver, new IntentFilter(SMS_SENT_ACTION)); + if (Build.VERSION.SDK_INT >= 33) { + mContext.registerReceiver(receiver, new IntentFilter(SMS_SENT_ACTION), null, + null, + Context.RECEIVER_EXPORTED); + } else { + mContext.registerReceiver(receiver, new IntentFilter(SMS_SENT_ACTION)); + } mSmsManager.sendTextMessage( /* destinationAddress= */ phoneNumber, /* scAddress= */ null, diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/TelephonySnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/TelephonySnippet.java index 21c5d1e..e4d33ec 100644 --- a/src/main/java/com/google/android/mobly/snippet/bundled/TelephonySnippet.java +++ b/src/main/java/com/google/android/mobly/snippet/bundled/TelephonySnippet.java @@ -17,24 +17,49 @@ package com.google.android.mobly.snippet.bundled; import android.content.Context; +import android.os.Build; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import androidx.test.platform.app.InstrumentationRegistry; import com.google.android.mobly.snippet.Snippet; import com.google.android.mobly.snippet.rpc.Rpc; +import com.google.android.mobly.snippet.rpc.RpcDefault; /** Snippet class for telephony RPCs. */ public class TelephonySnippet implements Snippet { private final TelephonyManager mTelephonyManager; + private final SubscriptionManager mSubscriptionManager; public TelephonySnippet() { Context context = InstrumentationRegistry.getInstrumentation().getContext(); mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + mSubscriptionManager = + (SubscriptionManager) + context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); } - @Rpc(description = "Gets the line 1 phone number.") - public String getLine1Number() { - return mTelephonyManager.getLine1Number(); + @Rpc( + description = + "Gets the line 1 phone number, or optionally get phone number for the " + + "simSlot (slot# start from 0, only valid for API level > 32)") + public String getLine1Number(@RpcDefault("0") Integer simSlot) { + String thisNumber = ""; + + if (Build.VERSION.SDK_INT < 33) { + thisNumber = mTelephonyManager.getLine1Number(); + } else { + SubscriptionInfo mSubscriptionInfo = + mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex( + simSlot.intValue()); + if (mSubscriptionInfo != null) { + thisNumber = + mSubscriptionManager.getPhoneNumber(mSubscriptionInfo.getSubscriptionId()); + } + } + + return thisNumber; } @Rpc(description = "Returns the unique subscriber ID, for example, the IMSI for a GSM phone.") @@ -44,10 +69,27 @@ public class TelephonySnippet implements Snippet { @Rpc( description = - "Gets the call state for the default subscription. Call state values are" - + "0: IDLE, 1: RINGING, 2: OFFHOOK") - public int getTelephonyCallState() { - return mTelephonyManager.getCallState(); + "Gets the call state for the default subscription or optionally get the call" + + " state for the simSlot (slot# start from 0, only valid for API" + + " level > 30). Call state values are 0: IDLE, 1: RINGING, 2: OFFHOOK") + public int getTelephonyCallState(@RpcDefault("0") Integer simSlot) { + int thisState = -1; + + if (Build.VERSION.SDK_INT < 31) { + return mTelephonyManager.getCallState(); + } else { + SubscriptionInfo mSubscriptionInfo = + mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex( + simSlot.intValue()); + if (mSubscriptionInfo != null) { + thisState = + mTelephonyManager + .createForSubscriptionId(mSubscriptionInfo.getSubscriptionId()) + .getCallStateForSubscription(); + } + } + + return thisState; } @Rpc( @@ -55,7 +97,11 @@ public class TelephonySnippet implements Snippet { "Returns a constant indicating the radio technology (network type) currently" + "in use on the device for data transmission.") public int getDataNetworkType() { - return mTelephonyManager.getDataNetworkType(); + if (Build.VERSION.SDK_INT < 30) { + return mTelephonyManager.getNetworkType(); + } else { + return mTelephonyManager.getDataNetworkType(); + } } @Rpc( diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/WifiManagerSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/WifiManagerSnippet.java index 89a65d2..e457dc3 100644 --- a/src/main/java/com/google/android/mobly/snippet/bundled/WifiManagerSnippet.java +++ b/src/main/java/com/google/android/mobly/snippet/bundled/WifiManagerSnippet.java @@ -35,8 +35,6 @@ import com.google.android.mobly.snippet.bundled.utils.Utils; import com.google.android.mobly.snippet.rpc.Rpc; import com.google.android.mobly.snippet.rpc.RpcMinSdk; import com.google.android.mobly.snippet.util.Log; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import org.json.JSONArray; diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothAdapterSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothAdapterSnippet.java index 71061fe..07f51e2 100644 --- a/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothAdapterSnippet.java +++ b/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/BluetoothAdapterSnippet.java @@ -343,7 +343,7 @@ public class BluetoothAdapterSnippet implements Snippet { throws BluetoothAdapterSnippetException, InterruptedException, JSONException { ArrayList<Bundle> pairedDevices = new ArrayList<>(); for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) { - pairedDevices.add(mJsonSerializer.serializeBluetoothDevice(device)); + pairedDevices.add(JsonSerializer.serializeBluetoothDevice(device)); } return pairedDevices; } diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/PairingBroadcastReceiver.java b/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/PairingBroadcastReceiver.java index 69ae433..f704897 100644 --- a/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/PairingBroadcastReceiver.java +++ b/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/PairingBroadcastReceiver.java @@ -20,6 +20,7 @@ public class PairingBroadcastReceiver extends BroadcastReceiver { Utils.adaptShellPermissionIfRequired(mContext); } + @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) { diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/profiles/BluetoothHeadsetSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/profiles/BluetoothHeadsetSnippet.java new file mode 100644 index 0000000..71cb81d --- /dev/null +++ b/src/main/java/com/google/android/mobly/snippet/bundled/bluetooth/profiles/BluetoothHeadsetSnippet.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2024 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.google.android.mobly.snippet.bundled.bluetooth.profiles; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.content.IntentFilter; +import android.os.Bundle; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.google.android.mobly.snippet.Snippet; +import com.google.android.mobly.snippet.bundled.bluetooth.PairingBroadcastReceiver; +import com.google.android.mobly.snippet.bundled.utils.JsonSerializer; +import com.google.android.mobly.snippet.bundled.utils.Utils; +import com.google.android.mobly.snippet.rpc.Rpc; + +import java.util.ArrayList; +import java.util.Set; + +/** + * Custom exception class for handling exceptions within the BluetoothHeadsetSnippet. + * This exception is meant to encapsulate and convey specific error information related to + * BluetoothHeadsetSnippet operations. + */ +public class BluetoothHeadsetSnippet implements Snippet { + + private final JsonSerializer mJsonSerializer = new JsonSerializer(); + private static final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + private static class BluetoothHeadsetSnippetException extends Exception { + private static final long serialVersionUID = 1; + + /** + * Constructs a BluetoothHeadsetSnippetException with the specified detail message. + * + * @param msg The detail message providing information about the exception. + */ + BluetoothHeadsetSnippetException(String msg) { + super(msg); + } + } + + private BluetoothHeadset mBluetoothHeadset; + private static final int HEADSET = 1; + + private final BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() { + @Override + public void onServiceConnected(int var1, BluetoothProfile profile) { + if (var1 == HEADSET) { + mBluetoothHeadset = (BluetoothHeadset)profile; + } + } + @Override + public void onServiceDisconnected(int var1) { + if (var1 == HEADSET) { + mBluetoothHeadset = null; + } + } + }; + + public BluetoothHeadsetSnippet() throws Throwable { + IntentFilter filter = new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + filter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); + mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEADSET); + Utils.waitUntil(() -> mBluetoothHeadset != null, 60); + mContext.registerReceiver(new PairingBroadcastReceiver(mContext), filter); + } + + + /** + * Returns the connection state for a Bluetooth device with the specified name. + * + * @param deviceAddress The address of the Bluetooth device. + * @return The connection state for the specified device. + * @throws BluetoothHeadsetSnippetException If no device with the specified name is connected via HEADSET. + */ + @Rpc(description = "Returns connection state.") + public int btHfpGetConnectionState(String deviceAddress) throws BluetoothHeadsetSnippetException { + Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); + for (BluetoothDevice device : pairedDevices) { + if (device.getAddress().equalsIgnoreCase(deviceAddress)) { + return mBluetoothHeadset.getConnectionState(device); + } + } + throw new BluetoothHeadsetSnippetException("No device with name " + deviceAddress +" is connected via HEADSET."); + } + + /** + * Starts voice recognition for the Bluetooth device with the specified name. + * + * @param deviceAddress The address of the Bluetooth device. + * @return True if voice recognition is successfully started; false otherwise. + * @throws BluetoothHeadsetSnippetException If no device with the specified name is found or if an error + * occurs during the startVoiceRecognition operation. + */ + @Rpc(description = "Starts voice recognition.") + public boolean btHfpStartVoiceRecognition(String deviceAddress) throws BluetoothHeadsetSnippetException{ + Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); + for (BluetoothDevice device : pairedDevices) { + if (device.getAddress().equalsIgnoreCase(deviceAddress)) { + return mBluetoothHeadset.startVoiceRecognition(device); + } + } + throw new BluetoothHeadsetSnippetException("No device with name " + deviceAddress +" is connected via HEADSET."); + } + + + /** + * Stops voice recognition for the Bluetooth device with the specified name. + * + * @param deviceAddress The address of the Bluetooth device. + * @return True if voice recognition is successfully started; false otherwise. + * @throws BluetoothHeadsetSnippetException If no device with the specified name is found or if an error + * occurs during the startVoiceRecognition operation. + */ + @Rpc(description = "Stops voice recognition.") + public boolean btHfpStopVoiceRecognition(String deviceAddress) throws BluetoothHeadsetSnippetException { + Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); + for (BluetoothDevice device : pairedDevices) { + if (device.getAddress().equalsIgnoreCase(deviceAddress)) { + return mBluetoothHeadset.stopVoiceRecognition(device); + } + } + throw new BluetoothHeadsetSnippetException("No device with name " + deviceAddress +" is connected via HEADSET."); + } + + @Rpc(description = "Checks whether the headset supports voice recognition;") + public boolean btHfpIsVoiceRecognitionSupported(String deviceAddress) throws BluetoothHeadsetSnippetException { + Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); + for (BluetoothDevice device : pairedDevices) { + if (device.getAddress().equalsIgnoreCase(deviceAddress)) { + return mBluetoothHeadset.isVoiceRecognitionSupported(device); + } + } + throw new BluetoothHeadsetSnippetException("No device with name " + deviceAddress +" is connected via HEADSET."); + } + @Rpc(description = "Gets all the devices currently connected via HFP profile.") + public ArrayList<Bundle> btHfpGetConnectedDevices() { + return mJsonSerializer.serializeBluetoothDeviceList(mBluetoothHeadset.getConnectedDevices()); + } + + @Override + public void shutdown() { } +} diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonDeserializer.java b/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonDeserializer.java index a3d5325..c7d5a91 100644 --- a/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonDeserializer.java +++ b/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonDeserializer.java @@ -101,7 +101,8 @@ public class JsonDeserializer { JSONObject manufacturerData = jsonObject.getJSONObject("ManufacturerData"); int manufacturerId = manufacturerData.getInt("ManufacturerId"); byte[] manufacturerSpecificData = - Base64.decode(jsonObject.getString("ManufacturerSpecificData"), Base64.DEFAULT); + Base64.decode( + manufacturerData.getString("ManufacturerSpecificData"), Base64.DEFAULT); builder.addManufacturerData(manufacturerId, manufacturerSpecificData); } return builder.build(); |