diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-04-18 16:37:40 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-04-18 16:37:40 +0000 |
commit | 7cedea5aee779e3c65ac0d2ae75f2b1ade821580 (patch) | |
tree | c9dbedc4226ddfcd242b516d47dbe1eda739c331 | |
parent | bf0ab4efcd65dd76545014943e6e466b2a14e4eb (diff) | |
parent | 2cedb0bb4826265431a258a166a9ab05ccc4d191 (diff) | |
download | mobly-bundled-snippets-7cedea5aee779e3c65ac0d2ae75f2b1ade821580.tar.gz |
Snap for 11730948 from 2cedb0bb4826265431a258a166a9ab05ccc4d191 to emu-35-1-release
Change-Id: Ief47bc97d4343905b2096325cc36ce431e046717
10 files changed, 258 insertions, 32 deletions
@@ -1,19 +1,20 @@ -name: "mobly-bundled-snippets" -description: - "Mobly Bundled Snippets is a set of Snippets to allow Mobly tests to " - "control Android devices by exposing a simplified version of the public " - "Android API suitable for testing." +# This project was upgraded with external_updater. +# Usage: tools/external_updater/updater.sh update external/mobly-bundled-snippets +# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md +name: "mobly-bundled-snippets" +description: "Mobly Bundled Snippets is a set of Snippets to allow Mobly tests to control Android devices by exposing a simplified version of the public Android API suitable for testing." third_party { - url { - type: HOMEPAGE - value: "https://github.com/google/mobly-bundled-snippets" + license_type: NOTICE + last_upgrade_date { + year: 2024 + month: 4 + day: 9 } - url { - type: GIT + homepage: "https://github.com/google/mobly-bundled-snippets" + identifier { + type: "Git" value: "https://github.com/google/mobly-bundled-snippets" + version: "4c30f2e6d835ac95b47e0fdfdfaffe5e9e63e4f6" } - version: "363a22ae26a277dfbf6c7a0c6596d1a7c08a39f1" - last_upgrade_date { year: 2023 month: 7 day: 26 } - license_type: NOTICE } diff --git a/build.gradle b/build.gradle index 99839ee..c88f93c 100644 --- a/build.gradle +++ b/build.gradle @@ -29,12 +29,12 @@ allprojects { apply plugin: 'com.android.application' android { - compileSdkVersion 31 + compileSdk 33 defaultConfig { applicationId "com.google.android.mobly.snippet.bundled" - minSdkVersion 26 - targetSdkVersion 31 + minSdk 26 + targetSdk 33 versionCode 1 versionName "0.0.1" setProperty("archivesBaseName", "mobly-bundled-snippets") diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 8c13914..211104d 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -11,8 +11,11 @@ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> - <uses-permission android:name="android.permission.BLUETOOTH" /> - <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + + <!-- Request legacy Bluetooth permissions on older devices. --> + <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> @@ -47,6 +50,7 @@ com.google.android.mobly.snippet.bundled.bluetooth.BluetoothGattServerSnippet, com.google.android.mobly.snippet.bundled.bluetooth.profiles.BluetoothA2dpSnippet, com.google.android.mobly.snippet.bundled.bluetooth.profiles.BluetoothHearingAidSnippet, + com.google.android.mobly.snippet.bundled.bluetooth.profiles.BluetoothHeadsetSnippet, com.google.android.mobly.snippet.bundled.BluetoothLeAdvertiserSnippet, com.google.android.mobly.snippet.bundled.BluetoothLeScannerSnippet, com.google.android.mobly.snippet.bundled.LogSnippet, 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(); |