diff options
Diffstat (limited to 'src')
6 files changed, 113 insertions, 23 deletions
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 795c063..08341c3 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -11,7 +11,9 @@ <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/AudioSnippet.java b/src/main/java/com/google/android/mobly/snippet/bundled/AudioSnippet.java index 9b4874f..10a2c07 100644 --- a/src/main/java/com/google/android/mobly/snippet/bundled/AudioSnippet.java +++ b/src/main/java/com/google/android/mobly/snippet/bundled/AudioSnippet.java @@ -104,6 +104,24 @@ public class AudioSnippet implements Snippet { AudioManager.STREAM_VOICE_CALL, value, 0 /* flags, 0 = no flags */); } + @Rpc(description = "Gets the alarm volume.") + public Integer getAlarmVolume() { + return mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM); + } + + @Rpc(description = "Gets the maximum alarm volume value.") + public int getAlarmMaxVolume() { + return mAudioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM); + } + + @Rpc( + description = + "Sets the alarm stream volume. The minimum value is 0. Use 'getAlarmMaxVolume'" + + " to determine the maximum.") + public void setAlarmVolume(Integer value) { + mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, value, 0 /* flags, 0 = no flags */); + } + @Rpc(description = "Silences all audio streams.") public void muteAll() throws Exception { /* Get numStreams from AudioSystem through reflection. If for some reason this fails, @@ -129,6 +147,9 @@ public class AudioSnippet implements Snippet { setMusicVolume(0); } + @Rpc(description = "Mute alarm stream.") + public void muteAlarm() { setAlarmVolume(0); } + @Override public void shutdown() {} } 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 cf577c3..7e1a416 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 @@ -88,8 +88,18 @@ public class WifiManagerSnippet implements Snippet { failedConfigs.add(config); } } + + // If removeNetwork is called on a network with both an open and OWE config, it will remove + // both. The subsequent call on the same network will fail. The clear operation may succeed + // even if failures appear in the log below. if (!failedConfigs.isEmpty()) { - throw new WifiManagerSnippetException("Failed to remove networks: " + failedConfigs); + Log.e("Encountered error while removing networks: " + failedConfigs); + } + + // Re-check configured configs list to ensure that it is cleared + unremovedConfigs = mWifiManager.getConfiguredNetworks(); + if (!unremovedConfigs.isEmpty()) { + throw new WifiManagerSnippetException("Failed to remove networks: " + unremovedConfigs); } } 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 6e66e43..c16a2b0 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 @@ -25,6 +25,10 @@ import android.content.IntentFilter; import android.os.Build; import android.os.Bundle; import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.BySelector; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.Until; import com.google.android.mobly.snippet.Snippet; import com.google.android.mobly.snippet.bundled.utils.JsonSerializer; import com.google.android.mobly.snippet.bundled.utils.Utils; @@ -34,6 +38,7 @@ import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; import org.json.JSONException; /** Snippet class exposing Android APIs in BluetoothAdapter. */ @@ -46,6 +51,10 @@ public class BluetoothAdapterSnippet implements Snippet { public BluetoothAdapterSnippetException(String msg) { super(msg); } + + public BluetoothAdapterSnippetException(String msg, Throwable err) { + super(msg, err); + } } // Timeout to measure consistent BT state. @@ -93,6 +102,16 @@ public class BluetoothAdapterSnippet implements Snippet { return null; } + /* Gets the UiDevice instance for UI operations. */ + private static UiDevice getUiDevice() throws BluetoothAdapterSnippetException { + try { + return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + } catch (IllegalStateException e) { + throw new BluetoothAdapterSnippetException("Failed to get UiDevice. Please ensure that " + + "no other UiAutomation service is running.", e); + } + } + @Rpc(description = "Enable bluetooth with a 30s timeout.") public void btEnable() throws BluetoothAdapterSnippetException, InterruptedException { if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { @@ -100,7 +119,19 @@ public class BluetoothAdapterSnippet implements Snippet { } waitForStableBtState(); - if (!mBluetoothAdapter.enable()) { + if (Build.VERSION.SDK_INT >= 33) { + // BluetoothAdapter#enable is removed from public SDK for 33 and above, so uses an + // intent instead. + UiDevice uiDevice = getUiDevice(); + Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + enableIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // Triggers the system UI popup to ask for explicit permission. + mContext.startActivity(enableIntent); + // Clicks the "ALLOW" button. + BySelector allowButtonSelector = By.text(TEXT_PATTERN_ALLOW).clickable(true); + uiDevice.wait(Until.findObject(allowButtonSelector), 10); + uiDevice.findObject(allowButtonSelector).click(); + } else if (!mBluetoothAdapter.enable()) { throw new BluetoothAdapterSnippetException("Failed to start enabling bluetooth."); } if (!Utils.waitUntil( @@ -200,7 +231,20 @@ public class BluetoothAdapterSnippet implements Snippet { throw new BluetoothAdapterSnippetException( "Bluetooth is not enabled, cannot become discoverable."); } - if (Build.VERSION.SDK_INT > 29) { + if (Build.VERSION.SDK_INT >= 31) { + // BluetoothAdapter#setScanMode is removed from public SDK for 31 and above, so uses an + // intent instead. + UiDevice uiDevice = getUiDevice(); + Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); + discoverableIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, duration); + // Triggers the system UI popup to ask for explicit permission. + mContext.startActivity(discoverableIntent); + // Clicks the "ALLOW" button. + BySelector allowButtonSelector = By.text(TEXT_PATTERN_ALLOW).clickable(true); + uiDevice.wait(Until.findObject(allowButtonSelector), 10); + uiDevice.findObject(allowButtonSelector).click(); + } else if (Build.VERSION.SDK_INT >= 30) { if (!(boolean) Utils.invokeByReflection( mBluetoothAdapter, @@ -221,6 +265,9 @@ public class BluetoothAdapterSnippet implements Snippet { } } + private static final Pattern TEXT_PATTERN_ALLOW = + Pattern.compile("allow", Pattern.CASE_INSENSITIVE); + @Rpc(description = "Cancel ongoing bluetooth discovery.") public void btCancelDiscovery() throws BluetoothAdapterSnippetException { if (!mBluetoothAdapter.isDiscovering()) { @@ -305,7 +352,7 @@ public class BluetoothAdapterSnippet implements Snippet { return; } } - throw new NoSuchElementException("No device wih address " + deviceAddress + " is paired."); + throw new NoSuchElementException("No device with address " + deviceAddress + " is paired."); } @Override diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonSerializer.java b/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonSerializer.java index 82e1e4f..82e6f7c 100644 --- a/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonSerializer.java +++ b/src/main/java/com/google/android/mobly/snippet/bundled/utils/JsonSerializer.java @@ -27,6 +27,7 @@ import android.net.wifi.WifiInfo; import android.os.Build; import android.os.Bundle; import android.os.ParcelUuid; +import android.util.SparseArray; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.lang.reflect.Modifier; @@ -41,17 +42,13 @@ import org.json.JSONObject; * A collection of methods used to serialize data types defined in Android API into JSON strings. */ public class JsonSerializer { - private static Gson mGson; - - public JsonSerializer() { - GsonBuilder builder = new GsonBuilder(); - mGson = - builder.serializeNulls() - .excludeFieldsWithModifiers(Modifier.STATIC) - .enableComplexMapKeySerialization() - .disableInnerClassSerialization() - .create(); - } + private static final Gson gson = + new GsonBuilder() + .serializeNulls() + .excludeFieldsWithModifiers(Modifier.STATIC) + .enableComplexMapKeySerialization() + .disableInnerClassSerialization() + .create(); /** * Remove the extra quotation marks from the beginning and the end of a string. @@ -89,11 +86,11 @@ public class JsonSerializer { * @throws JSONException */ private JSONObject defaultSerialization(Object data) throws JSONException { - return new JSONObject(mGson.toJson(data)); + return new JSONObject(gson.toJson(data)); } private JSONObject serializeDhcpInfo(DhcpInfo data) throws JSONException { - JSONObject result = new JSONObject(mGson.toJson(data)); + JSONObject result = new JSONObject(gson.toJson(data)); int ipAddress = data.ipAddress; byte[] addressBytes = { (byte) (0xff & ipAddress), @@ -111,14 +108,14 @@ public class JsonSerializer { } private JSONObject serializeWifiConfiguration(WifiConfiguration data) throws JSONException { - JSONObject result = new JSONObject(mGson.toJson(data)); + JSONObject result = new JSONObject(gson.toJson(data)); result.put("Status", WifiConfiguration.Status.strings[data.status]); result.put("SSID", trimQuotationMarks(data.SSID)); return result; } private JSONObject serializeWifiInfo(WifiInfo data) throws JSONException { - JSONObject result = new JSONObject(mGson.toJson(data)); + JSONObject result = new JSONObject(gson.toJson(data)); result.put("SSID", trimQuotationMarks(data.getSSID())); for (SupplicantState state : SupplicantState.values()) { if (data.getSupplicantState().equals(state)) { @@ -187,6 +184,20 @@ public class JsonSerializer { Bundle result = new Bundle(); result.putString("DeviceName", record.getDeviceName()); result.putInt("TxPowerLevel", record.getTxPowerLevel()); + result.putBundle( + "manufacturerSpecificData", serializeBleScanManufacturerSpecificData(record)); + return result; + } + + /** Serialize manufacturer specific data from ScanRecord for Bluetooth LE. */ + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private Bundle serializeBleScanManufacturerSpecificData(ScanRecord record) { + Bundle result = new Bundle(); + SparseArray<byte[]> sparseArray = record.getManufacturerSpecificData(); + for (int i = 0; i < sparseArray.size(); i++) { + int key = sparseArray.keyAt(i); + result.putByteArray(String.valueOf(key), sparseArray.get(key)); + } return result; } diff --git a/src/main/java/com/google/android/mobly/snippet/bundled/utils/RpcEnum.java b/src/main/java/com/google/android/mobly/snippet/bundled/utils/RpcEnum.java index d3d95ae..d6442a8 100644 --- a/src/main/java/com/google/android/mobly/snippet/bundled/utils/RpcEnum.java +++ b/src/main/java/com/google/android/mobly/snippet/bundled/utils/RpcEnum.java @@ -29,8 +29,8 @@ import com.google.common.collect.ImmutableBiMap; public class RpcEnum { private final ImmutableBiMap<String, Integer> mEnums; - private RpcEnum(ImmutableBiMap.Builder<String, Integer> builder, int minSdk) { - mEnums = builder.build(); + private RpcEnum(ImmutableBiMap.Builder<String, Integer> builder) { + mEnums = builder.buildOrThrow(); } /** @@ -64,7 +64,6 @@ public class RpcEnum { /** Builder for RpcEnum. */ public static class Builder { private final ImmutableBiMap.Builder<String, Integer> builder; - public int minSdk = 0; public Builder() { builder = new ImmutableBiMap.Builder<>(); @@ -83,7 +82,7 @@ public class RpcEnum { } public RpcEnum build() { - return new RpcEnum(builder, minSdk); + return new RpcEnum(builder); } } } |